/*
 * This file is part of Invenio.
 * Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 CERN.
 *
 * Invenio is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * Invenio is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Invenio; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
 */

/*
 * This is the main BibEdit Javascript.
 */

/* ************************* Table of contents ********************************
 *
 * 1. Global variables
 *
 * 2. Initialization
 *   - $()
 *   - initJeditable
 *   - initMisc
 *
 * 3. Ajax
 *   - initAjax
 *   - createReq
 *   - onAjaxError
 *   - onAjaxSuccess
 *
 * 4. Hash management
 *   - initStateFromHash
 *   - deserializeHash
 *   - changeAndSerializeHash
 *
 * 5. Data logic
 *   - getTagsSorted
 *   - getFieldPositionInTag
 *   - getPreviousTag
 *   - deleteFieldFromTag
 *   - cmpFields
 *   - fieldIsProtected
 *   - containsProtected
 *   - getMARC
 *   - getFieldTag
 *   - getSubfieldTag
 *   - validMARC
 *
 * 6. Record UI
 *   - onNewRecordClick
 *   - getRecord
 *   - onGetRecordSuccess
 *   - onSubmitClick
 *   - onCancelClick
 *   - onCloneRecordClick
 *   - onDeleteRecordClick
 *   - onMergeClick
 *   - bindNewRecordHandlers
 *   - cleanUp
 *   - positionBibEditPanel
 *
 * 7. Editor UI
 *   - colorFields
 *   - reColorFields
 *   - onMARCTagsClick
 *   - onHumanTagsClick
 *   - updateTags
 *   - onFieldBoxClick
 *   - onSubfieldBoxClick
 *   - onAddFieldClick
 *   - onAddFieldControlfieldClick
 *   - onAddFieldChange
 *   - onAddFieldSave
 *   - onAddSubfieldsClick
 *   - onAddSubfieldsChange
 *   - onAddSubfieldsSave
 *   - onDoubleClick
 *   - onContentChange
 *   - onMoveSubfieldClick
 *   - onDeleteClick
 */



/*
 * **************************** 1. Global variables ****************************
 */

// Record data
var gRecID = null;
var gRecIDLoading = null;
var gRecRev = null;
var gRecRevAuthor = null;
var gRecLatestRev = null;
var gRecord = null;
// Search results (record IDs)
var gResultSet = null;
// Current index in the result set
var gResultSetIndex = null;
// Tag format.
var gTagFormat = null;
// Has the record been modified?
var gRecordDirty = false;
// Last recorded cache modification time
var gCacheMTime = null;

// Are we navigating a set of records?
var gNavigatingRecordSet = false;

// The current hash (fragment part of the URL).
var gHash;
// The current hash deserialized to an object.
var gHashParsed;
// Hash check timer ID.
var gHashCheckTimerID;
// The previous and current state (this is not exactly the same as the state
// parameter, but an internal state control mechanism).
var gPrevState;
var gState;
// A current status
var gCurrentStatus;

// a global array of visible changes associated with a currently viewed record
// This array is cleared always when a new changes set is applied... then it is used
// for redrawing the change fields
// The index in this array is used when referring to a particular change [ like finding an appropriate box]

var gHoldingPenChanges = [];

// A global variable used to avoid multiple retrieving of the same changes stored in the Holding Pen
// this is the dictionary indexed by the HoldingPen entry identifiers and containing the javascript objects
// representing the records
// due to this mechanism, applying previously previewed changes, as well as previewing the change for the
// second time, can be made much faster
var gHoldingPenLoadedChanges = {};

// The changes that have been somehow processed and should not be displayed as already processed

var gDisabledHpEntries = {};

// is the read-only mode enabled ?
var gReadOnlyMode = false;

// revisions history
var gRecRevisionHistory = [];

var gUndoList = []; // list of possible undo operations
var gRedoList = []; // list of possible redo operations

// number of bibcirculation copies from the retrieval time
var gPhysCopiesNum = 0;
var gBibCircUrl = null;

var gDisplayBibCircPanel = false;
/*
 * **************************** 2. Initialization ******************************
 */

window.onload = function(){
  if (typeof(jQuery) == 'undefined'){
    alert('ERROR: jQuery not found!\n\n' +
    'The Record Editor requires jQuery, which does not appear to be ' +
    'installed on this server. Please alert your system ' +
    'administrator.\n\nInstructions on how to install jQuery and other ' +
    "required plug-ins can be found in Invenio's INSTALL file.");
    var imgError = document.createElement('img');
    imgError.setAttribute('src', '/img/circle_red.png');
    var txtError = document.createTextNode('jQuery missing');
    var cellIndicator = document.getElementById('cellIndicator');
    cellIndicator.replaceChild(imgError, cellIndicator.firstChild);
    var cellStatus = document.getElementById('cellStatus');
    cellStatus.replaceChild(txtError, cellStatus.firstChild);
  }
};

$(function(){
  /*
   * Initialize all components.
   */
  initMenu();
  initJeditable();
  initAjax();
  initMisc();
  initStateFromHash();
  gHashCheckTimerID = setInterval(initStateFromHash, gHASH_CHECK_INTERVAL);
  initHotkeys();
  initClipboardLibrary();
  initClipboard()
});


function failInReadOnly(){
  /** Function checking if the current BibEdit mode is read-only. In sucha a case, a warning
    dialog is displayed and true returned.
    If bibEdit is in read/write mode, false is returned
   */
  if (gReadOnlyMode === true){
    alert("It is impossible to perform this operation in the Read/Only mode. Please switch to Read-write mode before trying again");
    return true;
  }
  else{
    return false;
  }
}

function initJeditable(){
  /* Initialize Jeditable with the Autogrow extension. Used for in-place
   * content editing.
   */
  $.editable.addInputType('autogrow', {
    element: function(settings, original){
      var textarea = $('<textarea>');
      if (settings.rows){
        textarea.attr('rows', settings.rows);
      } else {
        textarea.height(settings.height);
      } if (settings.cols) {
        textarea.attr('cols', settings.cols);
      } else {
        textarea.width(settings.width);
      }
      $(this).append(textarea);
      return(textarea);
    },
    plugin: function(settings, original){
      $('textarea', this).autogrow(settings.autogrow);
    }
  });
}

function initClipboard(){
  // attaching the events -> handlers are stored in bibedit_engine.js file
  $(document).bind("copy", onPerformCopy);
  $(document).bind("paste", onPerformPaste);
}

function initMisc(){
  /*
   * Miscellaneous initialization operations.
   */
  // CERN allows for capital MARC indicators.
  if (gCERN_SITE){
    validMARC.reIndicator1 = /[\dA-Za-z]{1}/;
    validMARC.reIndicator2 = /[\dA-Za-z]{1}/;
  }

  // Warn user if BibEdit is being closed while a record is open.
  window.onbeforeunload = function(){
    if (gRecID && gRecordDirty){
      return '******************** WARNING ********************\n' +
             '                  You have unsubmitted changes.\n\n' +
             'You should go back to the page and click either:\n' +
             ' * Submit (to save your changes permanently)\n      or\n' +
             ' * Cancel (to discard your changes)';
    }
  };

  //Initialising the BibCircualtion integration plugin
  $("#bibEditBibCirculationBtn").bind("click", onBibCirculationBtnClicked);
}


/*
 * **************************** 3. Ajax ****************************************
 */

function initAjax(){
  /*
   * Initialize Ajax.
   */
  $.ajaxSetup(
    {cache: false,
      dataType: 'json',
      error: onAjaxError,
      type: 'POST',
      url: '/record/edit/'
    }
  );
}

function createReq(data, onSuccess, asynchronous){
  /*
   * Create Ajax request.
   */
  if (asynchronous == undefined){
    asynchronous = true;
  }
  // Include and increment transaction ID.
  var tID = createReq.transactionID++;
  createReq.transactions[tID] = data['requestType'];
  data.ID = tID;
  // Include cache modification time if we have it.
  if (gCacheMTime){
    data.cacheMTime = gCacheMTime;
  }
  // Send the request.
  $.ajax({data: {jsondata: JSON.stringify(data)},
           success: function(json){
                      onAjaxSuccess(json, onSuccess);
                    },
           async: asynchronous
  });
}
// Transactions data.
createReq.transactionID = 0;
createReq.transactions = [];

function createBulkReq(reqsData, onSuccess, optArgs){
  /* optArgs is a disctionary containning the optional arguments
     possible keys include:
       asynchronous : if the request should be asynchronous
       undoRedo : handler for the undo operation
  */
    // creating a bulk request ... the cache timestamp is not saved

    var data = { 'requestType' : 'applyBulkUpdates',
                 'requestsData' : reqsData,
                 'recID' : gRecID};
    if (optArgs.undoRedo != undefined){
        data.undoRedo = optArgs.undoRedo;
    }

    createReq(data, onSuccess, optArgs.asynchronous);
}

function onAjaxError(XHR, textStatus, errorThrown){
  /*
   * Handle Ajax request errors.
   */
  alert('Request completed with status ' + textStatus +
    '\nResult: ' + XHR.responseText +
    '\nError: ' + errorThrown);
}

function onAjaxSuccess(json, onSuccess){
  /*
   * Handle server response to Ajax requests, in particular error situations.
   * See BibEdit config for result codes.
   * If a function onSuccess is specified this will be called in the end,
   * if no error was encountered.
   */
  var resCode = json['resultCode'];
  var recID = json['recID'];
  if (resCode == 100){
    // User's session has timed out.
    gRecID = null;
    gRecIDLoading = null;
    window.location = recID ? gSITE_URL + '/record/' + recID + '/edit/'
      : gSITE_URL + '/record/edit/';
    return;
  }
  else if ($.inArray(resCode, [101, 102, 103, 104, 105, 106, 107, 108, 109])
     != -1){
    cleanUp(!gNavigatingRecordSet, null, null, true, true);
    if ($.inArray(resCode, [108, 109]) == -1)
      $('.headline').text('Record Editor: Record #' + recID);
    displayMessage(resCode);
    if (resCode == 107)
      return;
      $('#lnkGetRecord').bind('click', function(event){
        getRecord(recID);
        event.preventDefault();
      });
    updateStatus('error', gRESULT_CODES[resCode]);
  }
  else if (resCode == 110){
    displayMessage(resCode, true, [json['errors'].toString()]);
    $(document).scrollTop(0);
    updateStatus('error', gRESULT_CODES[resCode]);
  }
  else{
    var cacheOutdated = json['cacheOutdated'];
    var requestType = createReq.transactions[json['ID']];
    if (cacheOutdated && requestType == 'submit'){
      // User wants to submit, but cache is outdated. Outdated means that the
      // DB version of the record has changed after the cache was created.
      displayCacheOutdatedScreen(requestType);
      $('#lnkMergeCache').bind('click', onMergeClick);
      $('#lnkForceSubmit').bind('click', function(event){
  onSubmitClick.force = true;
  onSubmitClick();
  event.preventDefault();
      });
      $('#lnkDiscardChanges').bind('click', function(event){
  onCancelClick();
  event.preventDefault();
      });
      updateStatus('error', 'Error: Record cache is outdated');
    }
    else{
      if (requestType != 'getRecord'){
  // On getRecord requests the below actions will be performed in
  // onGetRecordSuccess (after cleanup).
  var cacheMTime = json['cacheMTime'];
  if (cacheMTime)
    // Store new cache modification time.
    gCacheMTime = cacheMTime;
  var cacheDirty = json['cacheDirty'];
  if (cacheDirty){
    // Cache is dirty. Enable submit button.
    gRecordDirty = cacheDirty;
    $('#btnSubmit').removeAttr('disabled');
    $('#btnSubmit').css('background-color', 'lightgreen');
  }
      }
      if (onSuccess)
  // No critical errors; call onSuccess function.
  onSuccess(json);
    }
  }
}

function resetBibeditState(){
  /** A function clearing the state of the bibEdit (all the panels content)
  */
  gHoldingPenLoadedChanges = {};
  gHoldingPenChanges = [];
  gDisabledHpEntries = {};
  gReadOnlyMode = false;
  gRecRevisionHistory = [];
  gUndoList = [];
  gRedoList = [];
  gPhysCopiesNum = 0;
  gBibCircUrl = null;

  updateRevisionsHistory();
  updateInterfaceAccordingToMode();
  updateRevisionsHistory();
  updateUrView();
  updateBibCirculationPanel();
  holdingPenPanelRemoveEntries();
}

/*
 * **************************** 4. Hash management *****************************
 */

function initStateFromHash(){
  /*
   * Initialize or update page state from hash.
   * Any program functions changing the hash should use changeAndSerializeHash()
   * which circumvents this function, meaning this function should only run on
   * page load and when browser navigation buttons (ie. Back and Forward) are
   * clicked. Any invalid hashes entered by the user will be ignored.
   */
  if (window.location.hash == gHash)
    // Hash is the same as last time we checked, do nothing.
    return;

  gHash = window.location.hash;
  gHashParsed = deserializeHash(gHash);
  gPrevState = gState;
  var tmpState = gHashParsed.state;
  var tmpRecID = gHashParsed.recid;
  var tmpRecRev = gHashParsed.recrev;
  var tmpReadOnlyMode = gHashParsed.romode;

  // Find out which internal state the new hash leaves us with
  if (tmpState && tmpRecID){
    // We have both state and record ID.
    if ($.inArray(tmpState, ['edit', 'submit', 'cancel', 'deleteRecord']) != -1)
  gState = tmpState;
    else
      // Invalid state, fail...
      return;
  }
  else if (tmpState){
    // We only have state.
    if (tmpState == 'edit')
      gState = 'startPage';
    else if (tmpState == 'newRecord')
      gState = 'newRecord';
    else
      // Invalid state, fail... (all states but 'edit' and 'newRecord' are
      // illegal without record ID).
      return;
  }
  else
    // Invalid hash, fail...
    return;

  if (gState != gPrevState
    || (gState == 'edit' && parseInt(tmpRecID) != gRecID) || // different record number
    (tmpRecRev != undefined && tmpRecRev != gRecRev) // different revision
    || (tmpRecRev == undefined && gRecRev != gRecLatestRev) // latest revision requested but another open
    || (tmpReadOnlyMode != gReadOnlyMode)){ // switched between read-only and read-write modes

    // We have an actual and legal change of state. Clean up and update the
    // page.
    updateStatus('updating');
    if (gRecID && !gRecordDirty && !tmpReadOnlyMode)
      // If the record is unchanged, delete the cache.
      createReq({recID: gRecID, requestType: 'deleteRecordCache'});
    switch (gState){
      case 'startPage':
        cleanUp(true, '', 'recID', true, true);
        updateStatus('ready');
        break;
      case 'edit':
        var recID = parseInt(tmpRecID);
        if (isNaN(recID)){
          // Invalid record ID.
          cleanUp(true, tmpRecID, 'recID', true);
          $('.headline').text('Record Editor: Record #' + tmpRecID);
          displayMessage(102);
          updateStatus('error', gRESULT_CODES[102]);
        }
        else{
          cleanUp(true, recID, 'recID');
          gReadOnlyMode = tmpReadOnlyMode;
            if (tmpRecRev != undefined && tmpRecRev != 0){
              getRecord(recID, tmpRecRev);
            } else {
              getRecord(recID);
            }
        }
      break;
    case 'newRecord':
      cleanUp(true, '', null, null, true);
      $('.headline').text('Record Editor: Create new record');
      displayNewRecordScreen();
      bindNewRecordHandlers();
      updateStatus('ready');
      break;
    case 'submit':
      cleanUp(true, '', null, true);
      $('.headline').text('Record Editor: Record #' + tmpRecID);
      displayMessage(4);
      updateStatus('ready');
      break;
    case 'cancel':
      cleanUp(true, '', null, true, true);
      updateStatus('ready');
      break;
    case 'deleteRecord':
      cleanUp(true, '', null, true);
      $('.headline').text('Record Editor: Record #' + tmpRecID);
      displayMessage(6);
      updateStatus('ready');
        break;
    }
  }
  else
  // What changed was not of interest, continue as if nothing happened.
    return;
}

function deserializeHash(aHash){
  /*
   * Deserializes a string (given as parameter or taken from the window object)
   * into the hash object.
   */
  if (aHash == undefined){
    aHash = window.location.hash;
  }
  var hash = {};
  var args = aHash.slice(1).split('&');
  var tmpArray;
  for (var i=0, n=args.length; i<n; i++){
    tmpArray = args[i].split('=');
    if (tmpArray.length == 2)
      hash[tmpArray[0]] = tmpArray[1];
  }
  return hash;
}

function changeAndSerializeHash(updateData){
  /*
   * Change the hash object to use the data from the object given as parameter.
   * Then update the hash accordingly, WITHOUT invoking initStateFromHash().
   */
  clearTimeout(gHashCheckTimerID);
  gHashParsed = {};
  for (var key in updateData){
    gHashParsed[key.toString()] = updateData[key].toString();
  }
  gHash = '#';
  for (key in gHashParsed){
    gHash += key + '=' + gHashParsed[key] + '&';
  }
  gHash = gHash.slice(0, -1);
  gState = gHashParsed.state;
  window.location.hash = gHash;
  gHashCheckTimerID = setInterval(initStateFromHash, gHASH_CHECK_INTERVAL);
}


/*
 * **************************** 5. Data logic **********************************
 */

function getTagsSorted(){
  /*
   * Return field tags in sorted order.
   */
  var tags = [];
  for (var tag in gRecord){
    tags.push(tag);
  }
  return tags.sort();
}

function getFieldPositionInTag(tag, field){
  /*
   * Determine the local (in tag) position of a new field.
   */
  var fields = gRecord[tag];
  if (fields){
    var fieldLength = fields.length, i = 0;
    while (i < fieldLength && cmpFields(field, fields[i]) != -1)
      i++;
    return i;
  }
  else
    return 0;
}

function getPreviousTag(tag){
  /*
   * Determine the previous tag in the record (if the given tag is the first
   * tag, 0 will be returned).
   */
  var tags = getTagsSorted();
  var tagPos = $.inArray(tag, tags);
  if (tagPos == -1){
    tags.push(tag);
    tags.sort();
    tagPos = $.inArray(tag, tags);
  }
  if (tagPos > 0)
    return tags[tagPos-1];
  return 0;
}

function deleteFieldFromTag(tag, fieldPosition){
  /*
   * Delete a specified field.
   */
  var field = gRecord[tag][fieldPosition];
  var fields = gRecord[tag];
  fields.splice($.inArray(field, fields), 1);
  // If last field, delete tag.
  if (fields.length == 0){
    delete gRecord[tag];
  }
}

function cmpFields(field1, field2){
  /*
   * Compare fields by indicators (tag assumed equal).
   */
  if (field1[1].toLowerCase() > field2[1].toLowerCase())
    return 1;
  else if (field1[1].toLowerCase() < field2[1].toLowerCase())
    return -1;
  else if (field1[2].toLowerCase() > field2[2].toLowerCase())
    return 1;
  else if (field1[2].toLowerCase() < field2[2].toLowerCase())
    return -1;
  return 0;
}

function insertFieldToRecord(record, fieldId, ind1, ind2, subFields){
  /**Inserting a new field on the client side and returning the position of the newly created field*/
  newField = [subFields, ind1, ind2, '', 0];
  if (record[fieldId] == undefined){
    record[fieldId] = [newField]
    return 0;
  } else {
    record[fieldId].push(newField);
    return (record[fieldId].length-1);
  }
}

function transformRecord(record){
  /**Transforming a bibrecord to a form that is easier to compare that is a dictionary
   * field identifier -> field indices -> fields list -> [subfields list, position in the record]
   *
   * The data is enriched with the positions inside the record in a following manner:
   * each field consists of:
   * */
  result = {};
  for (fieldId in record){
    result[fieldId] = {}
    indicesList = []; // a list of all the indices ... utilised later when determining the positions
    for (fieldIndex in record[fieldId]){

      indices =  "";
      if (record[fieldId][fieldIndex][1] == ' '){
        indices += "_";
      }else{
        indices += record[fieldId][fieldIndex][1]
      }

      if (record[fieldId][fieldIndex][2] == ' '){
        indices += "_";
      }else{
        indices += record[fieldId][fieldIndex][2]
      }

      if (result[fieldId][indices] == undefined){
        result[fieldId][indices] = []; // a future list of fields sharing the same indice
        indicesList.push(indices);
      }
      result[fieldId][indices].push([record[fieldId][fieldIndex][0], 0]);
    }

    // now calculating the positions within a field identifier ( utilised on the website )

    position = 0;

    indices = indicesList.sort();
    for (i in indices){
      for (fieldInd in result[fieldId][indices[i]]){
        result[fieldId][indices[i]][fieldInd][1] = position;
        position ++;
      }
    }
  }

    return result;
}

function filterChanges(changeset){
  /*Filtering the changes list -> removing the changes related to the fields
   * that should never be changed */
  unchangableTags = {"001" : true}; // a dictionary of the fields that should not be modified
  result = [];
  for (changeInd in changeset){
    change = changeset[changeInd];
    if ((change.tag == undefined) || (!(change.tag in unchangableTags))){
      result.push(change);
    }
  }
  return result;
}

///// Functions generating easy to display changes list

function compareFields(fieldId, indicators, fieldPos, field1, field2){
  result = [];
  for (sfPos in field2){
    if (field1[sfPos] == undefined){
      //  adding the subfield at the end of the record can be treated in a more graceful manner
      result.push(
          {"change_type" : "subfield_added",
           "tag" : fieldId,
           "indicators" : indicators,
           "field_position" : fieldPos,
           "subfield_code" : field2[sfPos][0],
           "subfield_content" : field2[sfPos][1]});
    }
    else
    {
      // the subfield exists in both the records
      if (field1[sfPos][0] != field2[sfPos][0]){
      //  a structural change ... we replace the entire field
        return [{"change_type" : "field_changed",
           "tag" : fieldId,
           "indicators" : indicators,
           "field_position" : fieldPos,
           "field_content" : field2}];
      } else
      {
        if (field1[sfPos][1] != field2[sfPos][1]){
          result.push({"change_type" : "subfield_changed",
            "tag" : fieldId,
            "indicators" : indicators,
            "field_position" : fieldPos,
            "subfield_position" : sfPos,
            "subfield_code" : field2[sfPos][0],
            "subfield_content" : field2[sfPos][1]});

        }
      }
    }
  }

  for (sfPos in field1){
    if (field2[sfPos] == undefined){
      result.push({"change_type" : "subfield_removed",
                "tag" : fieldId,
                "indicators" : indicators,
                "field_position" : fieldPos,
                "subfield_position" : sfPos});
    }
  }

  return result;
}

function compareIndicators(fieldId, indicators, fields1, fields2){
   /*a helper function allowing to compare inside one indicator
    * excluded from compareRecords for the code clarity reason*/
  result = []
  for (fieldPos in fields2){
    if (fields1[fieldPos] == undefined){
      result.push({"change_type" : "field_added",
                  "tag" : fieldId,
                  "indicators" : indicators,
                  "field_content" : fields2[fieldPos][0]});
    } else { // comparing the content of the subfields
      result = result.concat(compareFields(fieldId, indicators, fields1[fieldPos][1], fields1[fieldPos][0], fields2[fieldPos][0]));
    }
  }

  for (fieldPos in fields1){
    if (fields2[fieldPos] == undefined){
      fieldPosition = fields1[fieldPos][1];
      result.push({"change_type" : "field_removed",
             "tag" : fieldId,
             "indicators" : indicators,
             "field_position" : fieldPosition});
    }
  }
  return result;
}

function compareRecords(record1, record2){
  /*Compares two bibrecords, producing a list of atom changes that can be displayed
   * to the user if for example applying the Holding Pen change*/
   // 1) This is more convenient to have a different structure of the storage
  r1 = transformRecord(record1);
  r2 = transformRecord(record2);
  result = [];

  for (fieldId in r2){
    if (r1[fieldId] == undefined){
      for (indicators in r2[fieldId]){
        for (field in r2[fieldId][indicators]){
          result.push({"change_type" : "field_added",
                        "tag" : fieldId,
                        "indicators" : indicators,
                        "field_content" : r2[fieldId][indicators][field][0]});


        }
      }
    }
    else
    {
      for (indicators in r2[fieldId]){
        if (r1[fieldId][indicators] == undefined){
          for (field in r2[fieldId][indicators]){
            result.push({"change_type" : "field_added",
                         "tag" : fieldId,
                         "indicators" : indicators,
                         "field_content" : r2[fieldId][indicators][field][0]});


          }
        }
        else{
          result = result.concat(compareIndicators(fieldId, indicators,
              r1[fieldId][indicators], r2[fieldId][indicators]));
        }
      }

      for (indicators in r1[fieldId]){
        if (r2[fieldId][indicators] == undefined){
          for (fieldInd in r1[fieldId][indicators]){
            fieldPosition = r1[fieldId][indicators][fieldInd][1];
            result.push({"change_type" : "field_removed",
                 "tag" : fieldId,
                 "field_position" : fieldPosition});
          }

        }
      }

    }
  }

  for (fieldId in r1){
    if (r2[fieldId] == undefined){
      for (indicators in r1[fieldId]){
        for (field in r1[fieldId][indicators])
        {
          // field position has to be calculated here !!!
          fieldPosition = r1[fieldId][indicators][field][1]; // field position inside the mark
          result.push({"change_type" : "field_removed",
                       "tag" : fieldId,
                       "field_position" : fieldPosition});

        }
      }
    }
  }
  return result;
}

function fieldIsProtected(MARC){
  /*
   * Determine if a MARC field is protected or part of a protected group of
   * fields.
   */
  do{
    var i = MARC.length - 1;
    if ($.inArray(MARC, gPROTECTED_FIELDS) != -1)
      return true;
    MARC = MARC.substr(0, i);
    i--;
  }
  while (i >= 1)
  return false;
}

function containsProtectedField(fieldData){
  /*
   * Determine if a field data structure contains protected elements (useful
   * when checking if a deletion command is valid).
   * The data structure must be an object with the following levels
   * - Tag
   *   - Field position
   *     - Subfield index
   */
  var fieldPositions, subfieldIndexes, MARC;
  for (var tag in fieldData){
    fieldPositions = fieldData[tag];
    for (var fieldPosition in fieldPositions){
      subfieldIndexes = fieldPositions[fieldPosition];
      if (subfieldIndexes.length == 0){
  MARC = getMARC(tag, fieldPosition);
  if (fieldIsProtected(MARC))
    return MARC;
  }
      else{
  for (var i=0, n=subfieldIndexes.length; i<n; i++){
    MARC = getMARC(tag, fieldPosition, subfieldIndexes[i]);
    if (fieldIsProtected(MARC))
      return MARC;
  }
      }
    }
  }
  return false;
}

function getMARC(tag, fieldPosition, subfieldIndex){
  /*
   * Return the MARC representation of a field or a subfield.
   */
  var field = gRecord[tag][fieldPosition];
  var ind1, ind2;
  if (validMARC.reControlTag.test(tag))
    ind1 = '', ind2 = '';
  else{
    ind1 = (field[1] == ' ' || !field[1]) ? '_' : field[1];
    ind2 = (field[2] == ' ' || !field[2]) ? '_' : field[2];
  }
  if (subfieldIndex == undefined)
    return tag + ind1 + ind2;
  else
    return tag + ind1 + ind2 + field[0][subfieldIndex][0];
}

function getFieldTag(MARC){
  /*
   * Get the tag name of a field in format as specified by gTagFormat.
   */
  MARC = MARC.substr(0, 5);
  if (gTagFormat == 'human'){
    var tagName = gTAG_NAMES[MARC];
    if (tagName != undefined)
      // Direct hit. Return it.
      return tagName;
    else{
      // Start looking for wildcard hits.
      if (MARC.length == 3){
  // Controlfield
  tagName = gTAG_NAMES[MARC.substr(0, 2) + '%'];
  if (tagName != undefined && tagName != MARC + 'x')
    return tagName;
      }
      else{
  // Regular field, try finding wildcard hit by shortening expression
  // gradually. Ignores wildcards which gives values like '27x'.
  var term = MARC + '%', i = 5;
  do{
    tagName = gTAG_NAMES[term];
    if (tagName != undefined){
      if (tagName != MARC.substr(0, i) + 'x')
        return tagName;
      break;
    }
    i--;
    term = MARC.substr(0, i) + '%';
  }
  while (i >= 3)
      }
    }
  }
  return MARC;
}

function getSubfieldTag(MARC){
  /*
   * Get the tag name of a subfield in format as specified by gTagFormat.
   */
  if (gTagFormat == 'human'){
    var subfieldName = gTAG_NAMES[MARC];
      if (subfieldName != undefined)
  return subfieldName;
  }
  return '$$' + MARC.charAt(5);
}

function validMARC(datatype, value){
  /*
   * Validate a value of given datatype according to the MARC standard. The
   * value should be restricted/extended to it's expected size before being
   * passed to this function.
   * Datatype can be 'ControlTag', 'Tag', 'Indicator' or 'SubfieldCode'.
   * Returns a boolean.
   */
  return eval('validMARC.re' + datatype + '.test(value)');
}
// MARC validation REs
validMARC.reControlTag = /00[1-9A-Za-z]{1}/;
validMARC.reTag = /(0([1-9A-Z][0-9A-Z])|0([1-9a-z][0-9a-z]))|(([1-9A-Z][0-9A-Z]{2})|([1-9a-z][0-9a-z]{2}))/;
validMARC.reIndicator1 = /[\da-z]{1}/;
validMARC.reIndicator2 = /[\da-z]{1}/;
//validMARC.reSubfieldCode = /[\da-z!&quot;#$%&amp;'()*+,-./:;&lt;=&gt;?{}_^`~\[\]\\]{1}/;
validMARC.reSubfieldCode = /[\da-z!&quot;#$%&amp;'()*+,-.\/:;&lt;=&gt;?{}_^`~\[\]\\]{1}/;

/*
 * **************************** 6. Record UI ***********************************
 */

function onNewRecordClick(event){
  /*
   * Handle 'New' button (new record).
   */
  updateStatus('updating');
  if (gRecordDirty){
    if (!displayAlert('confirmLeavingChangedRecord')){
      updateStatus('ready');
      event.preventDefault();
      return;
    }
  }
  else
    // If the record is unchanged, erase the cache.
    if (gReadOnlyMode == false){
      createReq({recID: gRecID, requestType: 'deleteRecordCache'});
  }
  changeAndSerializeHash({state: 'newRecord'});
  cleanUp(true, '');
  $('.headline').text('Record Editor: Create new record');
  displayNewRecordScreen();
  bindNewRecordHandlers();
  updateStatus('ready');
  event.preventDefault();
}

function getRecord(recID, recRev, onSuccess){
  /* A function retrieving the bibliographic record, using an AJAX request.
   *
   * recID : the identifier of a record to be retrieved from the server
   * recRev : the revision of the record to be retrieved (0 or undefined
   *          means retrieving the newest version )
   * onSuccess : The callback to be executed upon retrieval. The default
   *             callback loads the retrieved record into the bibEdit user
   *             interface
   */

  // Temporary store the record ID by attaching it to the onGetRecordSuccess
  // function.
  if (onSuccess == undefined)
    onSuccess = onGetRecordSuccess;
  if (recRev != undefined && recRev != 0){
    changeAndSerializeHash({state: 'edit', recid: recID, recrev: recRev});
  }
  else{
    changeAndSerializeHash({state: 'edit', recid: recID});
  }

  gRecIDLoading = recID;

  reqData = {recID: recID,
             requestType: 'getRecord',
             deleteRecordCache:
             getRecord.deleteRecordCache,
             clonedRecord: getRecord.clonedRecord,
             inReadOnlyMode: gReadOnlyMode};

  if (recRev != undefined && recRev != 0){
    reqData.recordRevision = recRev;
    reqData.inReadOnlyMode = true;
  }

  resetBibeditState();
  createReq(reqData, onSuccess);

  onHoldingPenPanelRecordIdChanged(recID); // reloading the Holding Pen toolbar
  getRecord.deleteRecordCache = false;
  getRecord.clonedRecord = false;
}
// Enable this flag to delete any existing cache before fetching next record.
getRecord.deleteRecordCache = false;
// Enable this flag to tell that we are fetching a record that has just been
// cloned (enables proper feedback, highlighting).
getRecord.clonedRecord = false;


function onGetRecordSuccess(json){
  /*
   * Handle successfull 'getRecord' requests.
   */
  cleanUp(!gNavigatingRecordSet);
  // Store record data.
  gRecID = json['recID'];
  gRecIDLoading = null;
  gRecRev = json['recordRevision'];
  gRecRevAuthor = json['revisionAuthor'];
  gPhysCopiesNum = json['numberOfCopies'];
  gBibCircUrl = json['bibCirculationUrl'];
  gDisplayBibCircPanel = json['canRecordHavePhysicalCopies'];

  var revDt = formatDateTime(getRevisionDate(gRecRev));
  var recordRevInfo = "record revision: " + revDt;
  var revAuthorString = gRecRevAuthor;

  $('.headline').html(
    'Record Editor: Record #<span id="spnRecID">' + gRecID + '</span>' +
    '<div style="margin-left: 5px; font-size: 0.5em; color: #36c;">' +
    recordRevInfo + ' ' + revAuthorString + '</div>').css('white-space', 'nowrap');
  gRecord = json['record'];
  gTagFormat = json['tagFormat'];
  gRecordDirty = json['cacheDirty'];
  gCacheMTime = json['cacheMTime'];

  if (json['cacheOutdated']){
    // User had an existing outdated cache.
    displayCacheOutdatedScreen('getRecord');
    $('#lnkMergeCache').bind('click', onMergeClick);
    $('#lnkDiscardChanges').bind('click', function(event){
      getRecord.deleteRecordCache = true;
      getRecord(gRecID);
      event.preventDefault();
    });
    $('#lnkRemoveMsg').bind('click', function(event){
      $('#bibEditMessage').remove();
      event.preventDefault();
    });
  }

  gHoldingPenChanges = json['pendingHpChanges'];
  gDisabledHpEntries = json['disabledHpChanges'];
  gHoldingPenLoadedChanges = {};

  adjustHPChangesetsActivity();
  updateBibCirculationPanel();

  // updating the undo/redo lists
  gUndoList = json['undoList'];
  gRedoList = json['redoList'];
  updateUrView();

  // Display record.
  displayRecord();
  // Activate menu record controls.
  activateRecordMenu();
  // the current mode should is indicated by the result from the server
  gReadOnlyMode = (json['inReadOnlyMode'] != undefined) ? json['inReadOnlyMode'] : false;
  gRecLatestRev = (json['latestRevision'] != undefined) ? json['latestRevision'] : null;
  gRecRevisionHistory = (json['revisionsHistory'] != undefined) ? json['revisionsHistory'] : null;

  updateInterfaceAccordingToMode();

  if (gRecordDirty){
    $('#btnSubmit').removeAttr('disabled');
    $('#btnSubmit').css('background-color', 'lightgreen');
  }
  if (gTagFormat == 'MARC')
    $('#btnHumanTags').bind('click', onHumanTagsClick).removeAttr('disabled');
  else
    $('#btnMARCTags').bind('click', onMARCTagsClick).removeAttr('disabled');
  // Unfocus record selection field (to facilitate hotkeys).
  $('#txtSearchPattern').blur();
  if (json['resultCode'] == 9)
    $('#spnRecID').effect('highlight', {color: gCLONED_RECORD_COLOR},
      gCLONED_RECORD_COLOR_FADE_DURATION);
  updateStatus('report', gRESULT_CODES[json['resultCode']]);
  updateRevisionsHistory();
  adjustGeneralHPControlsVisibility();

  createReq({recID: gRecID, requestType: 'getTickets'}, onGetTicketsSuccess);

}

function onGetTemplateSuccess(json) {
  onGetRecordSuccess(json);
}

function onSubmitClick(){
  /*
   * Handle 'Submit' button (submit record).
   */
  updateStatus('updating');
  if (displayAlert('confirmSubmit')){
    createReq({recID: gRecID, requestType: 'submit',
         force: onSubmitClick.force}, function(json){
       // Submission was successful.
      changeAndSerializeHash({state: 'submit', recid: gRecID});
      var resCode = json['resultCode'];
      cleanUp(!gNavigatingRecordSet, '', null, true);
      updateStatus('report', gRESULT_CODES[resCode]);
      displayMessage(resCode);
      resetBibeditState()
    });
    onSubmitClick.force = false;
    resetBibeditState();
  }
  else
    updateStatus('ready');
}

// Enable this flag to force the next submission even if cache is outdated.
onSubmitClick.force = false;

function onCancelClick(){
  /*
   * Handle 'Cancel' button (cancel editing).
   */
  updateStatus('updating');
  if (!gRecordDirty || displayAlert('confirmCancel')) {
  createReq({
    recID: gRecID,
    requestType: 'cancel'
  }, function(json){
    // Cancellation was successful.
      changeAndSerializeHash({
          state: 'cancel',
          recid: gRecID
        });
        cleanUp(!gNavigatingRecordSet, '', null, true, true);
        updateStatus('report', gRESULT_CODES[json['resultCode']]);
      });
      holdingPenPanelRemoveEntries();
      gUndoList = [];
      gRedoList = [];
      gReadOnlyMode = false;
      gRecRevisionHistory = [];
      gHoldingPenLoadedChanges = [];
      gHoldingPenChanges = [];
      gPhysCopiesNum = 0;
      gBibCircUrl = null;
      // making the changes visible
      updateBibCirculationPanel();
      updateInterfaceAccordingToMode();
      updateRevisionsHistory();
      updateUrView();

    }
    else {
      updateStatus('ready');
    }
}

function onCloneRecordClick(){
  /*
   * Handle 'Clone' button (clone record).
   */
  updateStatus('updating');
  if (!displayAlert('confirmClone')){
    updateStatus('ready');
    return;
  }
  else if (!gRecordDirty)
    // If the record is unchanged, erase the cache.
    createReq({recID: gRecID, requestType: 'deleteRecordCache'});
  createReq({requestType: 'newRecord', newType: 'clone', recID: gRecID},
    function(json){
      var newRecID = json['newRecID'];
      $('#txtSearchPattern').val(newRecID);
      getRecord.clonedRecord = true;
      getRecord(newRecID);
  });
}

function onDeleteRecordClick(){
  /*
   * Handle 'Delete record' button.
   */
  if (gPhysCopiesNum > 0){
    displayAlert('errorPhysicalCopiesExist');
    return;
  }
  if (displayAlert('confirmDeleteRecord')){
    updateStatus('updating');
    createReq({recID: gRecID, requestType: 'deleteRecord'}, function(json){
      // Record deletion was successful.
      changeAndSerializeHash({state: 'deleteRecord', recid: gRecID});
      cleanUp(!gNavigatingRecordSet, '', null, true);
      var resCode = json['resultCode'];
      // now cleaning the interface - removing holding pen entries and record history
      resetBibeditState();
      updateStatus('report', gRESULT_CODES[resCode]);
      displayMessage(resCode);
    });
  }
}

function onMergeClick(event){
  /*
   * Handle click on 'Merge' link (to merge outdated cache with current DB
   * version of record).
   */
  notImplemented(event);

  updateStatus('updating');
  createReq({recID: gRecID, requestType: 'prepareRecordMerge'}, function(json){
    // Null gRecID to avoid warning when leaving page.
    gRecID = null;
    var recID = json['recID'];
    window.location = gSITE_URL + '/record/merge/#recid1=' + recID + '&recid2=' +
      'tmp';
  });
  event.preventDefault();
}

function bindNewRecordHandlers(){
  /*
   * Bind event handlers to links on 'Create new record' page.
   */
  $('#lnkNewEmptyRecord').bind('click', function(event){
    updateStatus('updating');
    createReq({requestType: 'newRecord', newType: 'empty'}, function(json){
      getRecord(json['newRecID']);
    });
    event.preventDefault();
  });
  for (var i=0, n=gRECORD_TEMPLATES.length; i<n; i++)
    $('#lnkNewTemplateRecord_' + i).bind('click', function(event){
      updateStatus('updating');
      var templateNo = this.id.split('_')[1];
      createReq({requestType: 'newRecord', newType: 'template',
	templateFilename: gRECORD_TEMPLATES[templateNo][0]}, function(json){
	  getRecord(json['newRecID'], 0, onGetTemplateSuccess); // recRev = 0 -> current revision
      });
      event.preventDefault();
    });
}

function cleanUp(disableRecBrowser, searchPattern, searchType,
     focusOnSearchBox, resetHeadline){
  /*
   * Clean up display and data.
   */
  // Deactivate controls.
  deactivateRecordMenu();
  if (disableRecBrowser){
    disableRecordBrowser();
    gResultSet = null;
    gResultSetIndex = null;
    gNavigatingRecordSet = false;
  }
  // Clear main content area.
  if (resetHeadline)
    $('.headline').text('Record Editor');
  $('#bibEditContent').empty();
  // Clear search area.
  if (typeof(searchPattern) == 'string' || typeof(searchPattern) == 'number')
    $('#txtSearchPattern').val(searchPattern);
  if ($.inArray(searchType, ['recID', 'reportnumber', 'anywhere']) != -1)
    $('#sctSearchType').val(searchPattern);
  if (focusOnSearchBox)
    $('#txtSearchPattern').focus();
  // Clear tickets.
  $('#tickets').empty();
  // Clear data.
  gRecID = null;
  gRecord = null;
  gTagFormat = null;
  gRecordDirty = false;
  gCacheMTime = null;
  gSelectionMode = false;
  gReadOnlyMode = false;
  gHoldingPenLoadedChanges = null;
  gHoldingPenChanges = null;
  gUndoList = [];
  gRedoList = [];
  gBibCircUrl = null;
  gPhysCopiesNum = 0;
}

function positionBibEditPanel(minimalPosition){
    /*
     * Dynamically position menu based on vertical scroll distance.
     */
    var newYscroll = $(document).scrollTop();
    // Only care if there has been some major scrolling.
    if (Math.abs(newYscroll - positionMenu.yScroll) > 10){
      // If scroll distance is less then 200px, position menu in sufficient
      // distance from header.
      if (newYscroll < 200)
        $('#bibEditMenu').animate({
    'top': 220 - newYscroll}, 'fast');
      // If scroll distance has crossed 200px, fix menu 50px from top.
      else if (positionMenu.yScroll < 200 && newYscroll > 200)
        $('#bibEditMenu').animate({
    'top': 50}, 'fast');
      positionMenu.yScroll = newYscroll;
    }
  }
/*
 * **************************** 7. Editor UI ***********************************
 */

function colorFields(){
  /*
   * Color every other field (rowgroup) gray to increase readability.
   */
  $('#bibEditTable tbody:even').each(function(){
    $(this).addClass('bibEditFieldColored');
  });
}

function reColorFields(){
  /*
   * Update coloring by removing existing, then recolor.
   */
  $('#bibEditTable tbody').each(function(){
    $(this).removeClass('bibEditFieldColored');
  });
  colorFields();
}

function onMARCTagsClick(event){
  /*
   * Handle 'MARC' link (MARC tags).
   */
  $(this).unbind('click').attr('disabled', 'disabled');
  createReq({recID: gRecID, requestType: 'changeTagFormat', tagFormat: 'MARC'});
  gTagFormat = 'MARC';
  updateTags();
  $('#btnHumanTags').bind('click', onHumanTagsClick).removeAttr('disabled');
  event.preventDefault();
}

function onHumanTagsClick(event){
  /*
   * Handle 'Human' link (Human tags).
   */
  $(this).unbind('click').attr('disabled', 'disabled');
  createReq({recID: gRecID, requestType: 'changeTagFormat',
       tagFormat: 'human'});
  gTagFormat = 'human';
  updateTags();
  $('#btnMARCTags').bind('click', onMARCTagsClick).removeAttr('disabled');
  event.preventDefault();
}

function updateTags(){
  /*
   * Check and update all tags (also subfield codes) against the currently
   * selected tag format.
   */
  $('.bibEditCellFieldTag').each(function(){
    var currentTag = $(this).text();
    var tmpArray = this.id.split('_');
    var tag = tmpArray[1], fieldPosition = tmpArray[2];
    var newTag = getFieldTag(getMARC(tag, fieldPosition));
    if (newTag != currentTag)
      $(this).text(newTag);
  });
  $('.bibEditCellSubfieldTag').each(function(){
    var currentTag = $(this).text();
    var tmpArray = this.id.split('_');
    var tag = tmpArray[1], fieldPosition = tmpArray[2],
      subfieldIndex = tmpArray[3];
    var newTag = getSubfieldTag(getMARC(tag, fieldPosition, subfieldIndex));
    if (newTag != currentTag)
      $(this).text(newTag);
  });
}

function onFieldBoxClick(box){
  /*
   * Handle field select boxes.
   */
  // Check/uncheck all subfield boxes, add/remove selected class.
  var rowGroup = $('#rowGroup_' + box.id.slice(box.id.indexOf('_')+1));
  if (box.checked){
    $(rowGroup).find('td[id^=content]').andSelf().addClass('bibEditSelected');
    if (gReadOnlyMode == false){
      $('#btnDeleteSelected').removeAttr('disabled');
    }
  }
  else{
    $(rowGroup).find('td[id^=content]').andSelf().removeClass(
      'bibEditSelected');
    if (!$('.bibEditSelected').length)
      // Nothing is selected, disable "Delete selected"-button.
      $('#btnDeleteSelected').attr('disabled', 'disabled');
  }
  $(rowGroup).find('input[type="checkbox"]').attr('checked', box.checked);
}

function onSubfieldBoxClick(box){
  /*
   * Handle subfield select boxes.
   */
  var tmpArray = box.id.split('_');
  var tag = tmpArray[1], fieldPosition = tmpArray[2],
    subfieldIndex = tmpArray[3];
  var fieldID = tag + '_' + fieldPosition;
  var subfieldID = fieldID + '_' + subfieldIndex;
  // If uncheck, uncheck field box and remove selected class.
  if (!box.checked){
    $('#content_' + subfieldID).removeClass('bibEditSelected');
    $('#boxField_' + fieldID).attr('checked', false);
    $('#rowGroup_' + fieldID).removeClass('bibEditSelected');
    if (!$('.bibEditSelected').length)
      // Nothing is selected, disable "Delete selected"-button.
      $('#btnDeleteSelected').attr('disabled', 'disabled');
  }
  // If check and all other subfield boxes checked, check field box, add
  // selected class.
  else{
    $('#content_' + subfieldID).addClass('bibEditSelected');
    var field = gRecord[tag][fieldPosition];
    if (field[0].length == $(
      '#rowGroup_' + fieldID + ' input[type=checkbox]' +
      '[class=bibEditBoxSubfield]:checked').length){
      $('#boxField_' + fieldID).attr('checked', true);
      $('#rowGroup_' + fieldID).addClass('bibEditSelected');
    }
    $('#btnDeleteSelected').removeAttr('disabled');
  }
}

function addFieldGatherInformations(fieldTmpNo){
  /** Gathering the information about a current form
      returns [template_num, data]
      This funcion saves the state of a form -> saving the template name and values only would
      not be enough. we want to know what has been modified in last-chosen template !
      data is in the same format as teh templates data.
  */
  var templateNum = $('#selectAddFieldTemplate_' + fieldTmpNo).attr("value");
  var tag = $("#txtAddFieldTag_" + fieldTmpNo).attr("value");

  // now checking if this is a controlfield ... controlfield if ind1 box is invisible
  if ($("#txtAddFieldInd1_" + fieldTmpNo + ":visible").length == 1){
    var ind1 = $("#txtAddFieldInd1_" + fieldTmpNo).attr("value");
    var ind2 = $("#txtAddFieldInd2_" + fieldTmpNo).attr("value");
    var subfieldTmpNo = $('#rowGroupAddField_' + fieldTmpNo).data('freeSubfieldTmpNo');
    var subfields = [];
    for (i=0;i<subfieldTmpNo;i++){
      var subfieldCode = $('#txtAddFieldSubfieldCode_' + fieldTmpNo + '_' + i).attr("value");
      var subfieldValue = $('#txtAddFieldValue_' + fieldTmpNo + '_' + i).attr("value");
      subfields.push([subfieldCode, subfieldValue]);
    }

    data = {
      "name": "nonexisting template - values taken from the field",
      "description": "The description of a template",
      "tag" : tag,
      "ind1" : ind1,
      "ind2" : ind2,
      "subfields" : subfields,
      "isControlfield" : false
    };
  } else {
    cfValue = $("#txtAddFieldValue_" + fieldTmpNo + "_0").attr("value");
    data = {
      "name": "nonexisting template - values taken from the field",
      "description": "The description of a template",
      "tag" : tag,
      "value" : cfValue,
      "isControlfield" : true
    }
  }

  return [templateNum, data];
}

function addFieldAddSubfieldEditor(jQRowGroupID, fieldTmpNo, defaultCode, defaultValue){
  /**
     Adding a subfield input control into the editor
     optional parameters:

     defaultCode - the subfield code that will be displayed
     defaultValue - the value that will be displayed by default in the editor
  */
  var subfieldTmpNo = $(jQRowGroupID).data('freeSubfieldTmpNo');
  $(jQRowGroupID).data('freeSubfieldTmpNo', subfieldTmpNo+1);

  var addFieldRows = $(jQRowGroupID + ' tr');

  $(addFieldRows).eq(addFieldRows.length-1).before(createAddFieldRow(
    fieldTmpNo, subfieldTmpNo, defaultCode, defaultValue));
  $('#txtAddFieldSubfieldCode_' + fieldTmpNo + '_' + subfieldTmpNo).bind(
    'keyup', onAddFieldChange);
  $('#btnAddFieldRemove_' + fieldTmpNo + '_' + subfieldTmpNo).bind('click', function(){
    $('#rowAddField_' + this.id.slice(this.id.indexOf('_')+1)).remove();
  });
  $('#txtAddFieldValue_' + fieldTmpNo + '_' + subfieldTmpNo).bind(
    'focus', function(){
      if ($(this).hasClass('bibEditVolatileSubfield')){
        $(this).select();
        $(this).removeClass("bibEditVolatileSubfield");
      }
    });
  var contentEditorId = '#txtAddFieldValue_' + fieldTmpNo + '_' + subfieldTmpNo;
  $(contentEditorId).bind('keyup', function(e){
    onAddFieldValueKeyPressed(e, jQRowGroupID, fieldTmpNo, subfieldTmpNo);
  });

}

function onAddFieldJumpToNextSubfield(jQRowGroupID, fieldTmpNo, subfieldTmpNo){
  // checking, how many subfields are there and if last, submitting the form
  var numberOfSubfields = $(jQRowGroupID).data('freeSubfieldTmpNo');
  if (subfieldTmpNo < (numberOfSubfields - 1)){
    var elementCode = "#txtAddFieldSubfieldCode_" + fieldTmpNo + "_" + (subfieldTmpNo + 1);
    $(elementCode)[0].focus();
  }
  else{
    addFieldSave(fieldTmpNo);
  }
}

function applyFieldTemplate(jQRowGroupID, formData, fieldTmpNo){
  /** A function that applies a template
      formNo is the number of addfield form that is treated at teh moment
      formData is the data of the field template
  */

  // first cleaning the existing fields

  $(jQRowGroupID).data('isControlfield', formData.isControlfield);
  if (formData.isControlfield){
    changeFieldToControlfield(fieldTmpNo);
    $("#txtAddFieldTag_" + fieldTmpNo).attr("value", formData.tag);
    $("#txtAddFieldInd1_" + fieldTmpNo).attr("value", '');
    $("#txtAddFieldInd2_" + fieldTmpNo).attr("value", '');
    $("#txtAddFieldValue_" + fieldTmpNo + "_0").attr("value", formData.value);
  }
  else
  {
    changeFieldToDatafield(fieldTmpNo);
    var subfieldTmpNo = $(jQRowGroupID).data('freeSubfieldTmpNo');
    $(jQRowGroupID).data('freeSubfieldTmpNo', 0);

    for (i=subfieldTmpNo-1; i>=0; i--){
      $('#rowAddField_' + fieldTmpNo + '_' + i).remove();
    }

    for (subfieldInd in formData.subfields){
      subfield = formData.subfields[subfieldInd];
      addFieldAddSubfieldEditor(jQRowGroupID, fieldTmpNo, subfield[0], subfield[1]);
    }

    // now changing the main field properties
    $("#txtAddFieldTag_" + fieldTmpNo).attr("value", formData.tag);
    $("#txtAddFieldInd1_" + fieldTmpNo).attr("value", formData.ind1);
    $("#txtAddFieldInd2_" + fieldTmpNo).attr("value", formData.ind2);
  }
}

function createAddFieldInterface(initialContent, initialTemplateNo){
  // Create form and scroll close to the top of the table.
  $(document).scrollTop(0);
  var fieldTmpNo = onAddFieldClick.addFieldFreeTmpNo++;
  var jQRowGroupID = '#rowGroupAddField_' + fieldTmpNo;
  $('#bibEditColFieldTag').css('width', '90px');
  var tbodyElements = $('#bibEditTable tbody');
  var insertionPoint = (tbodyElements.length >= 4) ? 3 : tbodyElements.length-1;
  $('#bibEditTable tbody').eq(insertionPoint).after(
    createAddFieldForm(fieldTmpNo, initialTemplateNo));
  $(jQRowGroupID).data('freeSubfieldTmpNo', 1);

  // Bind event handlers.
  $('#btnAddFieldAddSubfield_' + fieldTmpNo).bind('click', function(){
    addFieldAddSubfieldEditor(jQRowGroupID, fieldTmpNo, "", "");
  });
  $('#txtAddFieldTag_' + fieldTmpNo).bind('keyup', onAddFieldChange);
  $('#txtAddFieldInd1_' + fieldTmpNo).bind('keyup', onAddFieldChange);
  $('#txtAddFieldInd2_' + fieldTmpNo).bind('keyup', onAddFieldChange);
  $('#txtAddFieldSubfieldCode_' + fieldTmpNo + '_0').bind('keyup',
							  onAddFieldChange);
  $('#txtAddFieldValue_' + fieldTmpNo + '_0').bind('keyup', function (e){
    onAddFieldValueKeyPressed(e, jQRowGroupID, fieldTmpNo, 0);
  });

  $('#selectAddFieldTemplate_' + fieldTmpNo).bind('change', function(e){
      value = $('#selectAddFieldTemplate_' + fieldTmpNo).attr("value");
      applyFieldTemplate(jQRowGroupID, fieldTemplates[value], fieldTmpNo);
  });
  $('#selectAddSimilarFields_' + fieldTmpNo).bind('click', function(e){
    var data = addFieldGatherInformations(fieldTmpNo);
    var numRepetitions = parseInt($('#selectAddFieldTemplateTimes_' + fieldTmpNo).attr('value'));
    for (var i=0; i< numRepetitions; i++){
      createAddFieldInterface(data[1], data[0]);
    }
  });

  if (initialContent != undefined){
    applyFieldTemplate(jQRowGroupID, initialContent , fieldTmpNo);
  }else{
    $(jQRowGroupID).data('isControlfield', false);
  }

  reColorFields();
  $('#txtAddFieldTag_' + fieldTmpNo).focus();
  // Color the new form for a short period.
  $(jQRowGroupID).effect('highlight', {color: gNEW_ADD_FIELD_FORM_COLOR},
    gNEW_ADD_FIELD_FORM_COLOR_FADE_DURATION);

}

function onAddSubfieldValueKeyPressed(e, tag, fieldPosition, subfieldPosition){
  if (e.which == 13){
    // enter key pressed.
    var subfieldsNum = $('#rowGroup_' + tag + '_' + fieldPosition + ' .bibEditTxtSubfieldCode').length;
    if (subfieldPosition < (subfieldsNum - 1)){
      //jump to the next field
      $('#txtAddSubfieldsCode_' + tag + '_' + fieldPosition + '_' + (subfieldPosition + 1))[0].focus();
    } else {
      onAddSubfieldsSave(e, tag, fieldPosition);
    }
  }
  if (e.which == 27){
    // escape key pressed
    $('#rowAddSubfields_' + tag + '_' + fieldPosition + '_' + 0).nextAll().andSelf().remove();
  }
}

function onAddFieldValueKeyPressed(e, jQRowGroupID, fieldTmpNo, subfieldInd){
  if (e.which == 13){
    // enter key pressed
    onAddFieldJumpToNextSubfield(jQRowGroupID, fieldTmpNo, subfieldInd);
  }
  if (e.which == 27){
    // escape key pressed
    $(jQRowGroupID).remove();
    if (!$('#bibEditTable > [id^=rowGroupAddField]').length)
      $('#bibEditColFieldTag').css('width', '48px');
    reColorFields();
  }
}
function onAddFieldClick(){
  /*
   * Handle 'Add field' button.
   */
  if (failInReadOnly())
    return;
  createAddFieldInterface();
}

// Incrementing temporary field numbers.
onAddFieldClick.addFieldFreeTmpNo = 100000;

function changeFieldToControlfield(fieldTmpNo){
  /**
     Switching the field to be a control field
   */

  // removing additional entries
  var addFieldRows = $('#rowGroupAddField_' + fieldTmpNo + ' tr');
  $(addFieldRows).slice(2, addFieldRows.length-1).remove();

  // Clear all fields.
  var addFieldTextInput = $('#rowGroupAddField_' + fieldTmpNo +
          ' input[type=text]');
  $(addFieldTextInput).val('').removeClass('bibEditInputError');

  // Toggle hidden fields.
  var elems = $('#txtAddFieldInd1_' + fieldTmpNo + ', #txtAddFieldInd2_' +
    fieldTmpNo + ', #txtAddFieldSubfieldCode_' + fieldTmpNo + '_0,' +
    '#btnAddFieldAddSubfield_' + fieldTmpNo).hide();

  $('#txtAddFieldTag_' + fieldTmpNo).focus();
}

function changeFieldToDatafield(fieldTmpNo){
  /**
     Switching the field to be a datafield
   */
  // making the elements visible
  var elems = $('#txtAddFieldInd1_' + fieldTmpNo + ', #txtAddFieldInd2_' +
    fieldTmpNo + ', #txtAddFieldSubfieldCode_' + fieldTmpNo + '_0,' +
    '#btnAddFieldAddSubfield_' + fieldTmpNo).show();

  $('#txtAddFieldTag_' + fieldTmpNo).focus();
}

function onAddFieldChange(event){
  /*
   * Validate MARC and add or remove error class.
   */

  // first handling the case of escape key, which is a little different that others
  var fieldTmpNo = this.id.split('_')[1];

  if (event.which == 27){
    // escape key pressed
    var jQRowGroupID = "#rowGroupAddField_" + fieldTmpNo;
    $(jQRowGroupID).remove();
    if (!$('#bibEditTable > [id^=rowGroupAddField]').length)
      $('#bibEditColFieldTag').css('width', '48px');
    reColorFields();
  }
  else if (this.value.length == this.maxLength){
    var fieldType;
    if (this.id.indexOf('Tag') != -1){
      var jQRowGroupID = "#rowGroupAddField_" + fieldTmpNo;
      fieldType = ($(jQRowGroupID).data('isControlfield')) ? 'ControlTag' : 'Tag';
    }
    else if (this.id.indexOf('Ind1') != -1)
      fieldType = 'Indicator1';
    else if (this.id.indexOf('Ind2') != -1)
      fieldType = 'Indicator2';
    else
      fieldType = 'SubfieldCode';

    var valid = (((fieldType == 'Indicator1' || fieldType == 'Indicator2')
      && (this.value == '_' || this.value == ' '))
     || validMARC(fieldType, this.value));
    if (!valid && !$(this).hasClass('bibEditInputError'))
      $(this).addClass('bibEditInputError');
    else if (valid){
      if ($(this).hasClass('bibEditInputError'))
  $(this).removeClass('bibEditInputError');
      if (event.keyCode != 9 && event.keyCode != 16){
	switch(fieldType){
	  case 'ControlTag':
	    $(this).parent().nextAll().eq(3).children('input').focus();
	    break;
	  case 'Tag':
	  case 'Indicator1':
	    $(this).next().focus();
	    break;
	  case 'Indicator2':
          // in case the indicator is present, we can be sure this is not a control field... so we can safely jump to the subfield code input
          $('#txtAddFieldSubfieldCode_' + fieldTmpNo + '_0')[0].focus();
	    break;
	  case 'SubfieldCode':
	    $(this).parent().next().children('input').focus();
	    break;
	  default:
	    ;
	}
      }
    }
  }
  else if ($(this).hasClass('bibEditInputError'))
    $(this).removeClass('bibEditInputError');
}

function onAddFieldSave(event){
  var fieldTmpNo = this.id.split('_')[1];
  addFieldSave(fieldTmpNo);
}

function addFieldSave(fieldTmpNo)
{
  /*
   * Handle 'Save' button in add field form.
   */
  updateStatus('updating');

  var jQRowGroupID = "#rowGroupAddField_" + fieldTmpNo;
  var controlfield = $(jQRowGroupID).data('isControlfield');
  var tag = $('#txtAddFieldTag_' + fieldTmpNo).val();
  var value = $('#txtAddFieldValue_' + fieldTmpNo + '_0').val();
  var subfields = [], ind1 = ' ', ind2 = ' ';

  if (controlfield){
    // Controlfield. Validate and prepare to update.
    if (fieldIsProtected(tag)){
      displayAlert('alertAddProtectedField', [tag]);
      updateStatus('ready');
      return;
    }
    if (!validMARC('ControlTag', tag) || value == ''){
      displayAlert('alertCriticalInput');
      updateStatus('ready');
      return;
    }
    var field = [[], ' ', ' ', value, 0];
    var fieldPosition = getFieldPositionInTag(tag, field);
  }
  else{
    // Regular field. Validate and prepare to update.
    ind1 = $('#txtAddFieldInd1_' + fieldTmpNo).val();
    ind1 = (ind1 == '' || ind1 == '_') ? ' ' : ind1;
    ind2 = $('#txtAddFieldInd2_' + fieldTmpNo).val();
    ind2 = (ind2 == '' || ind2 == '_') ? ' ' : ind2;
    var MARC = tag + ind1 + ind2;
    if (fieldIsProtected(MARC)){
      displayAlert('alertAddProtectedField', [MARC]);
      updateStatus('ready');
      return;
    }
    var validInd1 = (ind1 == ' ' || validMARC('Indicator1', ind1));
    var validInd2 = (ind2 == ' ' || validMARC('Indicator2', ind2));
    if (!validMARC('Tag', tag)
  || !validInd1
  || !validInd2){
      displayAlert('alertCriticalInput');
      updateStatus('ready');
      return;
    }
    // Collect valid subfields in an array.
    var invalidOrEmptySubfields = false;
     $('#rowGroupAddField_' + fieldTmpNo + ' .bibEditTxtSubfieldCode'
      ).each(function(){
        var subfieldTmpNo = this.id.slice(this.id.lastIndexOf('_')+1);
        var txtValue = $('#txtAddFieldValue_' + fieldTmpNo + '_' +
    subfieldTmpNo);
        var value = $(txtValue).val();
        var isStillVolatile = txtValue.hasClass('bibEditVolatileSubfield');

        if (!$(this).hasClass('bibEditInputError')
          && this.value != ''
	  && !$(txtValue).hasClass('bibEditInputError')
          && value != ''){
            if (!isStillVolatile){
              subfields.push([this.value, value]);
            }
        }
        else
          invalidOrEmptySubfields = true;
      });

    if (invalidOrEmptySubfields){
      if (!subfields.length){
  // No valid subfields.
  displayAlert('alertCriticalInput');
  updateStatus('ready');
  return;
      }
      else if (!displayAlert('confirmInvalidOrEmptyInput')){
  updateStatus('ready');
  return;
      }
    }

    if (subfields[0] == undefined){
      displayAlert('alertEmptySubfieldsList');
      return;
    }
    var field = [subfields, ind1, ind2, '', 0];
    var fieldPosition = getFieldPositionInTag(tag, field);
  }

  // adding an undo handler
  var undoHandler = prepareUndoHandlerAddField(tag,
                                               ind1,
                                               ind2,
                                               fieldPosition,
                                               subfields,
                                               controlfield,
                                               value);
  addUndoOperation(undoHandler);

  // Create Ajax request.
  var data = {
    recID: gRecID,
    requestType: 'addField',
    controlfield: controlfield,
    fieldPosition: fieldPosition,
    tag: tag,
    ind1: ind1,
    ind2: ind2,
    subfields: subfields,
    value: value,
    undoRedo: undoHandler
  };
  createReq(data, function(json){
    updateStatus('report', gRESULT_CODES[json['resultCode']]);
  });

  // Continue local updating.
  var fields = gRecord[tag];
  // New field?
  if (!fields)
    gRecord[tag] = [field];
  else{
    fields.splice(fieldPosition, 0, field);
  }
  // Remove form.
  $('#rowGroupAddField_' + fieldTmpNo).remove();
  if (!$('#bibEditTable > [id^=rowGroupAddField]').length)
      $('#bibEditColFieldTag').css('width', '48px');
  // Redraw all fields with the same tag and recolor the full table.
  redrawFields(tag);
  reColorFields();
  // Scroll to and color the new field for a short period.
  var rowGroup = $('#rowGroup_' + tag + '_' + fieldPosition);
  $(document).scrollTop($(rowGroup).position().top - $(window).height()*0.5);
  $(rowGroup).effect('highlight', {color: gNEW_CONTENT_COLOR},
         gNEW_CONTENT_COLOR_FADE_DURATION);
}


function onAddSubfieldsClick(img){
  /*
   * Handle 'Add subfield' buttons.
   */
  var fieldID = img.id.slice(img.id.indexOf('_')+1);
  addSubfield(fieldID);
}

function addSubfield(fieldID, defSubCode, defValue) {
  /* add a subfield based on fieldID, where the first 3 digits are
   * the main tag, followed by _ and the position of the field.
   * defSubCode = the default value for subfield code
  */
  var jQRowGroupID = '#rowGroup_' + fieldID;
  var tmpArray = fieldID.split('_');
  var tag = tmpArray[0];var fieldPosition = tmpArray[1];
  if ($('#rowAddSubfieldsControls_' + fieldID).length == 0){
    // The 'Add subfields' form does not exist for this field.
    $(jQRowGroupID).append(createAddSubfieldsForm(fieldID, defSubCode, defValue));
    $(jQRowGroupID).data('freeSubfieldTmpNo', 1);
    $('#txtAddSubfieldsCode_' + fieldID + '_' + 0).bind('keyup',
      onAddSubfieldsChange);
    $('#txtAddSubfieldsValue_' + fieldID + '_0').bind('keyup', function (e){
      onAddSubfieldValueKeyPressed(e, tag, fieldPosition, 0);
    });
    $('#txtAddSubfieldsCode_' + fieldID + '_' + 0).focus();
  }
  else{
    // The 'Add subfields' form exist for this field. Just add another row.
    var subfieldTmpNo = $(jQRowGroupID).data('freeSubfieldTmpNo');
    $(jQRowGroupID).data('freeSubfieldTmpNo', subfieldTmpNo+1);
    var subfieldTmpID = fieldID + '_' + subfieldTmpNo;
    $('#rowAddSubfieldsControls_' + fieldID).before(
      createAddSubfieldsRow(fieldID, subfieldTmpNo));
    $('#txtAddSubfieldsCode_' + subfieldTmpID).bind('keyup',
      onAddSubfieldsChange);
    $('#btnAddSubfieldsRemove_' + subfieldTmpID).bind('click', function(){
      $('#rowAddSubfields_' + subfieldTmpID).remove();
    });
    $('#txtAddSubfieldsValue_' + subfieldTmpID).bind('keyup', function (e){
      onAddSubfieldValueKeyPressed(e, tag, fieldPosition, subfieldTmpNo);
    });
  }
}

function onAddSubfieldsChange(event){
  /*
   * Validate subfield code and add or remove error class.
   */
  if (this.value.length == 1){
    var valid = validMARC('SubfieldCode', this.value);
    if (!valid && !$(this).hasClass('bibEditInputError'))
      $(this).addClass('bibEditInputError');
    else if (valid){
      if ($(this).hasClass('bibEditInputError'))
  $(this).removeClass('bibEditInputError');
      if (event.keyCode != 9 && event.keyCode != 16){
  $(this).parent().next().children('input').focus();
      }
    }
  }
  else if ($(this).hasClass('bibEditInputError'))
    $(this).removeClass('bibEditInputError');
}

function onAddSubfieldsSave(event, tag, fieldPosition){
  /*
   * Handle 'Save' button in add subfields form.
   */
  updateStatus('updating');

//  var tmpArray = this.id.split('_');
//  var tag = tmpArray[1], fieldPosition = tmpArray[2];
  var fieldID = tag + '_' + fieldPosition;
  var subfields = [];
  var protectedSubfield = false, invalidOrEmptySubfields = false;
  // Collect valid fields in an array.
  $('#rowGroup_' + fieldID + ' .bibEditTxtSubfieldCode'
   ).each(function(){
     var MARC = getMARC(tag, fieldPosition) + this.value;
     if ($.inArray(MARC, gPROTECTED_FIELDS) != -1){
       protectedSubfield = MARC;
       return false;
     }
     var subfieldTmpNo = this.id.slice(this.id.lastIndexOf('_')+1);
     var txtValue = $('#txtAddSubfieldsValue_' + fieldID + '_' +
       subfieldTmpNo);
     var value = $(txtValue).val();
     if (!$(this).hasClass('bibEditInputError')
   && this.value != ''
   && !$(txtValue).hasClass('bibEditInputError')
   && value != '')
       subfields.push([this.value, value]);
     else
       invalidOrEmptySubfields = true;
  });

  // Report problems, like protected, empty or invalid fields.
  if (protectedSubfield){
    displayAlert('alertAddProtectedSubfield');
    updateStatus('ready');
    return;
  }
  if (invalidOrEmptySubfields && !displayAlert('confirmInvalidOrEmptyInput')){
    updateStatus('ready');
    return;
  }

  if (!subfields.length == 0){
     // creating the undo/redo handler
    var urHandler = prepareUndoHandlerAddSubfields(tag, fieldPosition, subfields);
    addUndoOperation(urHandler);
    // Create Ajax request
    var data = {
      recID: gRecID,
      requestType: 'addSubfields',
      tag: tag,
      fieldPosition: fieldPosition,
      subfields: subfields,
      undoRedo: urHandler
    };
    createReq(data, function(json){
      updateStatus('report', gRESULT_CODES[json['resultCode']]);
    });

    // Continue local updating
    var field = gRecord[tag][fieldPosition];
    field[0] = field[0].concat(subfields);
    var rowGroup  = $('#rowGroup_' + fieldID);
    var coloredRowGroup = $(rowGroup).hasClass('bibEditFieldColored');
    $(rowGroup).replaceWith(createField(tag, field, fieldPosition));
    if (coloredRowGroup)
      $('#rowGroup_' + fieldID).addClass('bibEditFieldColored');

    // Color the new fields for a short period.
    var rows = $('#rowGroup_' + fieldID + ' tr');
    $(rows).slice(rows.length - subfields.length).effect('highlight', {
      color: gNEW_CONTENT_COLOR}, gNEW_CONTENT_COLOR_FADE_DURATION);
  }
  else{
    // No valid fields were submitted.
    $('#rowAddSubfields_' + fieldID + '_' + 0).nextAll().andSelf().remove();
    updateStatus('ready');
  }
}

function convertFieldIntoEditable(cell, shouldSelect){
  // chacking if the clicked field is still present int the DOM structure ... if not, we have just removed the element
  if ($(cell).parent().parent().parent()[0] == undefined){
    return;
  }
  // first we have to detach all exisiting editables ... which means detaching the event
  editEvent = 'click';
  $(cell).unbind(editEvent);

  $(cell).editable(
    function(value){
      newVal = onContentChange(value, this);
      if (newVal.substring(0,9) == "VOLATILE:"){
        $(cell).addClass("bibEditVolatileSubfield");
        newVal = newVal.substring(9);
        $(cell).addClass("bibEditVolatileSubfield");
        if (!shouldSelect){
          // the field should start selcting all the content upon the click
          convertFieldIntoEditable(cell, true);
        }
      }
      else{
        $(cell).removeClass("bibEditVolatileSubfield");
        if (shouldSelect){
          // this is a volatile field any more - clicking should not
          // select all the content inside.
          convertFieldIntoEditable(cell, false);
        }
      }

      return newVal;
    }, {
      type: 'autogrow',
      callback: function(data, settings){
        var tmpArray = this.id.split('_');
        var tag = tmpArray[1], fieldPosition = tmpArray[2],
        subfieldIndex = tmpArray[3];

        for (changeNum in gHoldingPenChanges){
          change =  gHoldingPenChanges[changeNum];
          if (change.tag == tag &&
              change.field_position == fieldPosition &&
              change.subfield_position != undefined &&
              change.subfield_position == subfieldIndex){
              addChangeControl(changeNum, true);
          }
        }
      },
      event: editEvent,
      data: function(){
        // Get the real content from the record structure (instead of
        // from the view, where HTML entities are escaped).
        var tmpArray = this.id.split('_');
        var tag = tmpArray[1], fieldPosition = tmpArray[2],
        subfieldIndex = tmpArray[3];
        var field = gRecord[tag][fieldPosition];
        var tmpResult = "";
        if (subfieldIndex == undefined)
          // Controlfield
          tmpResult = field[3];
        else
          tmpResult = field[0][subfieldIndex][1];
        if (tmpResult.substring(0,9) == "VOLATILE:"){
          tmpResult = tmpResult.substring(9);
        }
        return tmpResult;
      },
      placeholder: '',
      width: '100%',
      onblur: 'submit',
      select: shouldSelect,
      autogrow: {
        lineHeight: 16,
        minHeight: 36
      }
    });
}

function onContentClick(cell){
  /*
   * Handle click on editable content fields.
   */
  // Check if subfield is volatile subfield from a template
  var shouldSelect = false;
  if ( $(cell).hasClass('bibEditVolatileSubfield') ){
    shouldSelect = true;
  }
  if (!$(cell).hasClass('edit_area')){
    $(cell).addClass('edit_area').removeAttr('onclick');
    convertFieldIntoEditable(cell, shouldSelect);
    $(cell).trigger('click');
  }
}

function getUpdateSubfieldValueRequestData(tag, fieldPosition, subfieldIndex, subfieldCode, value, changeNo, undoDescriptor, restoreChange){
  var data = {
    recID: gRecID,
    requestType: 'modifyContent',
    tag: tag,
    fieldPosition: fieldPosition,
    subfieldIndex: subfieldIndex,
    subfieldCode: subfieldCode,
    value: value
  };
  if (changeNo != undefined && changeNo != -1){
    data.hpChanges = {toDisable: [changeNo]};
  }
  if (undoDescriptor != undefined && undoDescriptor != null){
    data.undoRedo = undoDescriptor;
  }
  return data;
}

function updateSubfieldValue(tag, fieldPosition, subfieldIndex, subfieldCode, value, consumedChange, undoDescriptor){
  updateStatus('updating');
  // Create Ajax request for simple updating the subfield value
  if (consumedChange == undefined || consumedChange == null){
    consumedChange = -1;
  }

  var data = getUpdateSubfieldValueRequestData(tag,
                                               fieldPosition,
                                               subfieldIndex,
                                               subfieldCode,
                                               value,
                                               consumedChange,
                                               undoDescriptor);

  createReq(data, function(json){
    updateStatus('report', gRESULT_CODES[json['resultCode']]);
  });
}

/*call autosuggest, get the values, suggest them to the user*/
/*this is typically called when autosuggest key is pressed*/
function onAutosuggest(event) {
  var mytarget = event.target;
  if (event.srcElement) mytarget = event.srcElement;/*fix for IE*/
  var myparent = mytarget.parentNode;
  var mygrandparent = myparent.parentNode;
  var parentid = myparent.id;
  var value = mytarget.value;
  var mylen = value.length;
  var replacement = ""; //used by autocomplete
  var tmpArray = mygrandparent.id.split('_');
  //alert("parentid "+ parentid +" grannyid "+ mygrandparent.id);
  /*ids for autosuggest/autocomplete html elements*/
  var content_id = 'content_'+tmpArray[1]+'_'+tmpArray[2]+'_'+tmpArray[3];
  var autosuggest_id = 'autosuggest_'+tmpArray[1]+'_'+tmpArray[2]+'_'+tmpArray[3];
  var select_id = 'select_'+tmpArray[1]+'_'+tmpArray[2]+'_'+tmpArray[3];
  var maintag = tmpArray[1], fieldPosition = tmpArray[2],
	  subfieldIndex = tmpArray[3];
  var field = gRecord[maintag][fieldPosition];
  var subfieldcode = field[0][subfieldIndex][0];
  var subtag1 = field[1];
  var subtag2 = field[2];
  //check if this an autosuggest or autocomplete field.
  var fullcode = getMARC(maintag, fieldPosition, subfieldIndex);
  var reqtype = ""; //autosuggest or autocomplete, according to tag..
  for (var i=0;i<gAUTOSUGGEST_TAGS.length;i++) { if (fullcode == gAUTOSUGGEST_TAGS[i]) { reqtype = "autosuggest" }}
  for (var i=0;i<gAUTOCOMPLETE_TAGS.length;i++) { if (fullcode == gAUTOCOMPLETE_TAGS[i]) { reqtype = "autocomplete" }}
  if (fullcode == gKEYWORD_TAG) { reqtype = "autokeyword" }
  if (reqtype == "") {
    return;
  }

  // Create Ajax request.
  var data = {
    recID: gRecID,
    maintag: maintag,
    subtag1: subtag1,
    subtag2: subtag2,
    subfieldcode: subfieldcode,
    requestType: reqtype,
    value: value
  }; //reqtype is autosuggest, autocomplete or autokeyword
  createReq(data, function(json){
    updateStatus('report', gRESULT_CODES[json['resultCode']]);
    suggestions = json[reqtype];
    if (reqtype == 'autocomplete') {
        if ((suggestions != null) && (suggestions.length > 0)) {
            //put the first one "here"
            replacement = suggestions[0];
            var myelement = document.getElementById(mygrandparent.id);
            if (myelement != null) {
               //put in the the gRecord
               gRecord[maintag][fieldPosition][0][subfieldIndex][1] = replacement;
               mytarget.value = replacement;
            }
            //for the rest, create new subfields
            for (var i=1;i<suggestions.length;i++) {
                var valuein = suggestions[i];
                var addhereID = maintag+"_"+fieldPosition; //an id to indicate where the new subfield goes
                addSubfield(addhereID, subfieldcode, valuein);
            }
        } else { //autocomplete, nothing found
            alert("No suggestions for your search term "+value);
        }
    } //autocomplete
    if ((reqtype == 'autosuggest') || (reqtype == 'autokeyword')) {
        if ((suggestions != null) && (suggestions.length > 0)) {
            /*put the suggestions in the div autosuggest_xxxx*/
            //make a nice box..
            mysel = '<table width="400" border="0"><tr><td><span class="bibeditscrollArea"><ul>';
            //create the select items..
            for (var i=0;i<suggestions.length;i++) {
               tmpid = select_id+"-"+suggestions[i];
               mysel = mysel +'<li onClick="onAutosuggestSelect(\''+tmpid+'\');">'+suggestions[i]+"</li>";
            }
            mysel = mysel+"</ul></td>"
            //add a stylish close link in case the user does not find
            //the value among the suggestions
            mysel = mysel + "<td><form><input type='button' value='close' onClick='onAutosuggestSelect(\""+select_id+"-"+'\");></form></td>';
            mysel = mysel+"</tr></table>";
            //for (var i=0;i<suggestions.length;i++) { mysel = mysel + +suggestions[i]+ " "; }
            autosugg_in = document.getElementById(autosuggest_id);
            if (autosugg_in != null) { autosugg_in.innerHTML = mysel; }
         } else { //there were no suggestions
             alert("No suggestions for your search term "+value);
         }
    } //autosuggest
  }, false); /*NB! This function is called synchronously.*/
} //onAutoSuggest


/*put the content of the autosuggest select into the field where autoselect was lauched*/
function onAutosuggestSelect(selectidandselval){
  /*first take the selectid. It is the string before the first hyphen*/
  var tmpArray = selectidandselval.split('-');
  var selectid = tmpArray[0];
  var selval =  tmpArray[1];
  /*generate the content element id and autosuggest element id from the selectid*/
  var tmpArray = selectid.split('_');
  var content_id = 'content_'+tmpArray[1]+'_'+tmpArray[2]+'_'+tmpArray[3];
  var autosuggest_id = 'autosuggest_'+tmpArray[1]+'_'+tmpArray[2]+'_'+tmpArray[3];
  var content_t = document.getElementById(content_id); //table
  var content = null; //the actual text
  //this is interesting, since if the user is browsing the list of selections by mouse,
  //the autogrown form has disapperaed and there is only the table left.. so check..
  if (content_t.innerHTML.indexOf("<form>") ==0) {
     var content_f = null; //form
     var content_ta = null; //textarea
     if (content_t) {
         content_f = content_t.firstChild; //form is the sub-elem of table
     }
     if (content_f) {
         content_ta = content_f.firstChild; //textarea is the sub-elem of form
     }
     if (!(content_ta)) { return; }
     content = content_ta;
  } else {
     content = content_t;
  }
  /*put value in place*/
  if (selval) {
      content.innerHTML = selval;
      content.value = selval;
  }
  /*remove autosuggest box*/
  var autosugg_in = document.getElementById(autosuggest_id);
  autosugg_in.innerHTML = "";
}

function onContentChange(value, th){
  /*
   * Handle 'Save' button in editable content fields.
   */
  if (failInReadOnly()){
    return;
  }
  var tmpArray = th.id.split('_');
  var tag = tmpArray[1], fieldPosition = tmpArray[2], subfieldIndex = tmpArray[3];
  var field = gRecord[tag][fieldPosition];
  var oldValue = "";
  value = value.replace(/\n/g, ' '); // Replace newlines with spaces.
  if (subfieldIndex == undefined){
    // Controlfield
    if (field[3] == value)
      return escapeHTML(value);
    oldValue = field[3];
    field[3] = value;
    subfieldIndex = null;
    var subfieldCode = null;
  }
  else{
    if (field[0][subfieldIndex][1] == value)
      return escapeHTML(value);
    // Regular field
    oldValue = field[0][subfieldIndex][1];
    field[0][subfieldIndex][1] = value;
    var subfieldCode = field[0][subfieldIndex][0];
  }

  // setting the undo/redo handler
  var newValue = escapeHTML(value);
  var code = gRecord[tag][fieldPosition][0][subfieldIndex][0];
  urHandler = prepareUndoHandlerChangeSubfield(tag,
                                               fieldPosition,
                                               subfieldIndex,
                                               oldValue,
                                               newValue,
                                               code, code);
  addUndoOperation(urHandler);

  // generating the Ajax request

  updateSubfieldValue(tag, fieldPosition, subfieldIndex, subfieldCode, value, null, urHandler);

  setTimeout('$("#content_' + tag + '_' + fieldPosition + '_' + subfieldIndex +
      '").effect("highlight", {color: gNEW_CONTENT_COLOR}, ' +
      'gNEW_CONTENT_COLOR_FADE_DURATION)', gNEW_CONTENT_HIGHLIGHT_DELAY);

  // Return escaped value to display.
  return newValue;
}

function onMoveSubfieldClick(type, tag, fieldPosition, subfieldIndex){
  /*
   * Handle subfield moving arrows.
   */
  if (failInReadOnly()){
    return;
  }
  updateStatus('updating');

  // Check if moving is possible
  if (type == 'up') {
    if ( (parseInt(subfieldIndex) - 1 )< 0) {
      updateStatus('ready', '');
      return;
    }
  }
  else {
    if ((parseInt(subfieldIndex) + 1) >= gRecord[tag][fieldPosition][0].length) {
      updateStatus('ready', '');
      return;
    }
  }
  // creating the undoRedo Hanglers
  var undoHandler = prepareUndoHandlerMoveSubfields(tag, parseInt(fieldPosition), parseInt(subfieldIndex), type);
  addUndoOperation(undoHandler);

  var ajaxData = performMoveSubfield(tag, fieldPosition, subfieldIndex, type, undoHandler);
  createReq(ajaxData, function(json){
    updateStatus('report', gRESULT_CODES[json['resultCode']]);
  }, false);

}

function onDeleteClick(event){
  /*
   * Handle 'Delete selected' button or delete hotkeys.
   */
  if (failInReadOnly()){
    return;
  }
  updateStatus('updating');

  var toDelete = getSelectedFields();
  // Assert that no protected fields are scheduled for deletion.
  var protectedField = containsProtectedField(toDelete);
  if (protectedField){
    displayAlert('alertDeleteProtectedField', [protectedField]);
    updateStatus('ready');
    return;
  }
    // register the undo Handler
  var urHandler = prepareUndoHandlerDeleteFields(toDelete);
  addUndoOperation(urHandler);
  var ajaxData = deleteFields(toDelete, urHandler);

  createReq(ajaxData, function(json){
    updateStatus('report', gRESULT_CODES[json['resultCode']]);
  });
}

function onMoveFieldUp(tag, fieldPosition) {
  if (failInReadOnly()){
    return;
  }
  fieldPosition = parseInt(fieldPosition);
  var thisField = gRecord[tag][fieldPosition];
  if (fieldPosition > 0) {
    var prevField = gRecord[tag][fieldPosition-1];
    // check if the previous field has the same indicators
    if ( cmpFields(thisField, prevField) == 0 ) {
      var undoHandler = prepareUndoHandlerMoveField(tag, fieldPosition, "up");
      addUndoOperation(undoHandler);
      var ajaxData = performMoveField(tag, fieldPosition, "up", undoHandler);
      createReq(ajaxData, function(json){
        updateStatus('report', gRESULT_CODES[json['resultCode']]);
      }, false);
    }
  }
}

function onMoveFieldDown(tag, fieldPosition) {
  if (failInReadOnly()){
    return;
  }
  fieldPosition = parseInt(fieldPosition);
  var thisField = gRecord[tag][fieldPosition];
  if (fieldPosition < gRecord[tag].length-1) {
    var nextField = gRecord[tag][fieldPosition+1];
    // check if the next field has the same indicators
    if ( cmpFields(thisField, nextField) == 0 ) {
      var undoHandler = prepareUndoHandlerMoveField(tag, fieldPosition, "down");
      addUndoOperation(undoHandler);
      var ajaxData = performMoveField(tag, fieldPosition, "down", undoHandler);
      createReq(ajaxData, function(json){
        updateStatus('report', gRESULT_CODES[json['resultCode']]);
      }, false);
    }
  }
}



function updateInterfaceAccordingToMode(){
  /* updates the user interface (in particular the activity of menu buttons)
     accordingly to the surrent operation mode of BibEdit.
   */
  // updating the switch button caption
  if (gReadOnlyMode){
    deactivateRecordMenu();
    $('#btnSwitchReadOnly').attr("innerHTML", "R/W");
  } else {
    activateRecordMenu();
    $('#btnSwitchReadOnly').attr("innerHTML", "Read-only");
  }
}

function switchToReadOnlyMode(){
  // Moving to the read only mode with BibEdit

  if (gRecordDirty == true){
    alert("Please submit the record or cancel your changes before going to the read-only mode ");
    return false;
  }
  gReadOnlyMode = true;
  createReq({recID: gRecID, requestType: 'deleteRecordCache'});
  gCacheMTime = 0;

  updateInterfaceAccordingToMode();
}

function canSwitchToReadWriteMode(){
  /*A function determining if at current moment, it is possible to switch to the read/write mode*/
  // If the revision is not the newest -> return false
  return true;
}

function switchToReadWriteMode(){
  // swtching to a normal editing mode of BibEdit
  if (!canSwitchToReadWriteMode()){
    alert("It is not possible to switch to the editing mode at the moment");
    return false;
  }

  gReadOnlyMode = false;
  // reading the record as if it was just opened
  getRecord(gRecID);
  updateInterfaceAccordingToMode();
}


function onSwitchReadOnlyMode(){
  // an event habdler being executed when user clicks on the switch to read only mode button
  if (gReadOnlyMode){
    switchToReadWriteMode();
  } else {
    switchToReadOnlyMode();
  }
}


// functions handling the revisions history

function getCompareClickedHandler(revisionId){
  return function(e){
    //document.location = "/record/merge/#recid1=" + gRecID + "&recid2=" + gRecID + "." + revisionId;
    var comparisonUrl = "/record/edit/compare_revisions?recid=" +
      gRecID + "&rev1=" + gRecRev + "&rev2=" + revisionId;
    var newWindow = window.open(comparisonUrl);
    newWindow.focus();
    return false;
  };
}

function onRevertClick(revisionId){
  /*
   * Handle 'Revert' button (submit record).
   */
  updateStatus('updating');
  if (displayAlert('confirmRevert')){
    createReq({recID: gRecID, revId: revisionId, requestType: 'revert',
         force: onSubmitClick.force}, function(json){
    // Submission was successful.
      changeAndSerializeHash({state: 'submit', recid: gRecID});
      var resCode = json['resultCode'];
      cleanUp(!gNavigatingRecordSet, '', null, true);
      updateStatus('report', gRESULT_CODES[resCode]);
      displayMessage(resCode);
      // clear the list of record revisions
      resetBibeditState()
    });
    onSubmitClick.force = false;
  }
  else
    updateStatus('ready');
  holdingPenPanelRemoveEntries(); // clearing the holding pen entries list
}

function getRevertClickedHandler(revisionId){
  return function(e){
      onRevertClick(revisionId);
      return false;
  };
}

function updateRevisionsHistory(){
  if (gRecRevisionHistory == null){
      return;
  }

  var result = "";
  var results = [];
  for (revInd in  gRecRevisionHistory){
    tmpResult = displayRevisionHistoryEntry(gRecID, gRecRevisionHistory[revInd]);
    tmpResult["revisionID"] = gRecRevisionHistory[revInd];
    results.push(tmpResult);
    result += tmpResult["HTML"];
  }

  $("#bibEditRevisionsHistory").attr("innerHTML", result);
  $(".bibEditRevHistoryEntryContent").bind("click", function(evt){
    var revision = $(this)[0].id.split("_")[1];
    updateStatus('updating');
    getRecord(gRecID, revision);
  });

  /*Attaching the actions on user interface*/
  for (resultInd in results){
    result = results[resultInd];
    $('#' + result['compareImgId']).bind("click", getCompareClickedHandler(result["revisionID"]));
    $('#' + result['revertImgId']).bind("click", getRevertClickedHandler(result["revisionID"]));
  }
}

function encodeXml(str){
    var resultString = "";
    for (var i=0;i<str.length;i++){
        var c = str.charAt(i);
        switch (c){
        case '<':
            resultString += "&lt;";
            break;
        case '>':
            resultString += "&gt;";
            break;
        case '&':
            resultString += "&amp;";
            break;
        case '"':
            resultString += "&quot;";
            break;
        case "'":
            resultString += "&apos;";
            break;
        default:
            resultString += c;
        }
    }
    return resultString;
}

function getSelectionMarcXml(){
  /*Gets the MARC XML of the current editor selection*/

  var checkedFieldBoxes = $('input[class="bibEditBoxField"]:checked'); // interesting only for the controlfields
                                                                       //  where no subfields are
  var checkedSubfieldBoxes = $('input[class="bibEditBoxSubfield"]:checked');

  // now constructing the interesting data

  var selectionNormal = {}; // a dictionary of identifiers taht have appeared already

  var selectionControlFields = [];

  var selectedFields = []; // a list of fields already selected
  var currentField = null; // a curently edited field

  // Collect subfields to be deleted in toDelete.
  var normalFieldsXml = "";
  var controlFieldsXml = "";

  $(checkedSubfieldBoxes).each(function(){
    var tmpArray = this.id.split('_');
    var tag = tmpArray[1], fieldPosition = tmpArray[2], subfieldIndex = tmpArray[3];
    if (currentField == null || currentField.tag != tag || currentField.position != fieldPosition){
      if (currentField != null){
        var newPos = selectedFields.length;
        selectedFields[newPos] = currentField;
        normalFieldsXml += "</datafield>"
      }
      // creating an empty field
      currentField={};
      currentField.subfields = [];
      currentField.tag = tag;
      currentField.position = fieldPosition;
      currentField.ind1 = gRecord[tag][fieldPosition][1];
      currentField.ind2 = gRecord[tag][fieldPosition][2];
      currentField.isControlField = false;
      selectionNormal[tag] = true;
      normalFieldsXml += "<datafield tag=\"" + currentField.tag + "\" ind1=\"" +
          currentField.ind1 + "\" ind2=\"" + currentField.ind2 + "\">";
    }

    // appending a current subfield
    var newPos = currentField.subfields.length;
    subfield = gRecord[tag][fieldPosition][0][subfieldIndex];
    currentField.subfields[newPos] = subfield;
      normalFieldsXml += "<subfield code=\"" + subfield[0] + "\">" + encodeXml(subfield[1]) + "</subfield>";
  });

  if (currentField != null){
    var newPos = selectedFields.length;
    selectedFields[newPos] = currentField;
    normalFieldsXml += "</datafield>";
  }

  // now extending by the control fields (they did not appear earlier)
  $(checkedFieldBoxes).each(function(){
    var tmpArray = this.id.split('_');
    var tag = tmpArray[1], fieldPosition = tmpArray[2];
    if (selectionNormal[tag] == undefined){
       // we have a control field ! otherwise, the field has been already utilised
      currentField = {};
      currentField.tag = tag;
      currentField.value = gRecord[tag][fieldPosition][3]
      var newPos = selectionControlFields.length;
      selectionControlFields[newPos] = currentField;
      controlFieldsXml += "<controlfield tag=\"" + currentField.tag + "\">" + currentField.value+ "</controlfield>";
    }
  });

  return "<record>" + controlFieldsXml + normalFieldsXml + "</record>";

}

function onPerformCopy(){
  /** The handler performing the copy operation
   */
  if (document.activeElement.type == "textarea" || document.activeElement.type == "text"){
    /*we do not want to perform this in case we are in an ordinary text area*/
    return;
  }
  var valueToCopy = getSelectionMarcXml();
  clipboardCopyValue(valueToCopy);
}

function onPerformPaste(){
  /* Performing the paste operation -> retriexing the MARC XML from the clipboard,
     decoding and applying the code to the

     According to the default behaviour, the fields are appended as last of the same kind
   */

  if (document.activeElement.type == "textarea" || document.activeElement.type == "text"){
    /*we do not want to perform this in case we are in an ordinary text area*/
    return;
  }

  var clipboardContent = clipboardPasteValue();

  var record = null;
  try{
    record = decodeMarcXMLRecord(clipboardContent);
  } catch (err){
    alert("Error when parsing XML occured ... " + err.mesage);
  }

  var changesAdd = []; // the ajax requests for all the fields
  var undoHandlers = [];

  for (tag in record){
    if (gRecord[tag] == undefined){
      gRecord[tag] = [];
    }
    // now appending the fields
    for (fieldInd in record[tag]){
      newPos = gRecord[tag].length;
      gRecord[tag][newPos] = record[tag][fieldInd];
      // enqueue ajax add field request

      isControlfield = record[tag][fieldInd][0].length == 0;
      ind1 = record[tag][fieldInd][1];
      ind2 = record[tag][fieldInd][2];
      subfields = record[tag][fieldInd][0];
      value: record[tag][fieldInd][3]; // in case of a control field

      changesAdd.push({
        recID: gRecID,
        requestType: "addField",
        controlfield : isControlfield,
        fieldPosition : newPos,
        tag: tag,
        ind1: record[tag][fieldInd][1],
        ind2: record[tag][fieldInd][2],
        subfields: record[tag][fieldInd][0],
        value: record[tag][fieldInd][3]
      });

      undoHandler = prepareUndoHandlerAddField(
          tag, ind1, ind2, newPos, subfields, isControlfield, value);
      undoHandlers.push(undoHandler);
    }
  }

  undoHandlers.reverse();
  var undoHandler = prepareUndoHandlerBulkOperation(undoHandlers, "paste");
  addUndoOperation(undoHandler);
  // now sending the Ajax Request
  var optArgs = {
      undoRedo: undoHandler
  };

  createBulkReq(changesAdd, function(json){
      updateStatus('report', gRESULT_CODES[json['resultCode']])}, optArgs);

  // tags have to be redrawn in the increasing order

  tags = [];
  for (tag in record){
    tags.push(tag);
  }
  tags.sort();
  for (tagInd in tags){
      redrawFields(tags[tagInd]);
  }
  reColorFields();
}
function addUndoOperation(operation){
  gUndoList.push(operation);
  invalidateRedo();
  updateUrView();
}

function invalidateRedo(){
  /** Invalidates the redo list - after some modification*/
  gRedoList = [];
}

function adjustUndoRedoBtnsActivity(){
  /** Making the undo/redo buttons active/inactive according to the needs
   */
  if (gUndoList.length > 0){
    $("#btnUndo").addAttribute("disabled", "");
  }
  else{
    $("#btnUndo").removeAttr("disabled");
  }

  if (gRedoList.length > 0){
    $("#btnRedo").addAttribute("disabled", "");
  }
  else{
    $("#btnRedo").removeAttr("disabled");
  }
}


function undoMany(number){
  /** A function undoing many operations from the undo list

      Arguments:
        number: number of operations to undo
   */

  var undoOperations = []
  for (i=0;i<number;i++){
    undoOperations.push(getUndoOperation());
  }
  performUndoOperations(undoOperations);
  updateUrView();
}

function prepareUndoHandlerEmpty(){
  /** Creating an empty undo/redo handler - might be useful in some cases
      when undo operation is required but should not be registered
  */
  return {
    operation_type: "no_operation"
  };
}

function prepareUndoHandlerAddField(tag, ind1, ind2, fieldPosition, subfields,
                                    isControlField, value ){
  /** A function creating an undo handler for the operation of affing a new
      field

    Arguments:
      tag:            tag of the field
      ind1:           first indicator (a single character string)
      ind2:           second indicator (a single character string)
      fieldPosition:  a position of the field among other fields with the same
                      tag and possibly different indicators)
      subFields:      a list of fields subfields. each subfield is decribed by
                      a pair: [code, value]
      isControlField: a boolean value indicating if the field is a control field
      value:          a value of a control field. (important in case of passing
                      iscontrolField equal true)
  */

  var result = {};
  result.operation_type = "add_field";
  result.newSubfields = subfields;
  result.tag = tag;
  result.ind1 = ind1;
  result.ind2 = ind2;
  result.fieldPosition = fieldPosition;
  result.isControlField = isControlField;
  if (isControlField){
    // value == false means that we are dealing with a control field
    result.value = value;
  } else{
    result.subfields = subfields;
  }

  return result;
}

function prepareUndoHandlerVisualizeChangeset(changesetNumber, changesListBefore, changesListAfter){
  var result = {};
  result.operation_type = "visualize_hp_changeset";
  result.changesetNumber = changesetNumber;
  result.oldChangesList = changesListBefore;
  result.newChangesList = changesListAfter;
  return result;
}

function prepareUndoHandlerApplyHPChange(changeHandler, changeNo){
  /** changeHandler - handler to the original undo/redo handler associated with the action
   */
  var result = {};
  result.operation_type = "apply_hp_change";
  result.handler = changeHandler;
  result.changeNo = changeNo;
  result.changeType = gHoldingPenChanges[changeNo].change_type;
  return result;
}

function prepareUndoHandlerApplyHPChanges(changeHandlers, changesBefore){
  /** Producing the undo/redo handler associated with application of
      more than one HoldingPen change

      Arguments:
        changeHandlers - a list od undo/redo handlers associated with subsequent changes.
        changesBefore = a list of Holding Pen changes before the operation
   */

  var result = {};
  result.operation_type = "apply_hp_changes";
  result.handlers = changeHandlers;
  result.changesBefore = changesBefore;
  return result;
}

function prepareUndoHandlerRemoveAllHPChanges(hpChanges){
  /** A function preparing the undo handler associated with the
      removal of all the Holding Pen changes present in teh interface */
  var result = {};
  result.operation_type = "remove_all_hp_changes";
  result.old_changes_list = hpChanges;
  return result;
}

function prepareUndoHandlerBulkOperation(undoHandlers, handlerTitle){
  /*
    Preapring an und/redo handler allowing to treat the bulk operations
    ( like for example in case of pasting fields )
    arguments:
      undoHandlers : handlers of separate operations from the bulk
      handlerTitle : a message to be displayed in the undo menu
  */
  var result = {};

  result.operation_type = "bulk_operation";
  result.handlers = undoHandlers;
  result.title = handlerTitle;

  return result;
}

function urPerformAddSubfields(tag, fieldPosition, subfields, isUndo){
    var ajaxData = {
      recID: gRecID,
      requestType: 'addSubfields',
      tag: tag,
      fieldPosition: fieldPosition,
      subfields: subfields,
      undoRedo: (isUndo ? "undo": "redo")
    };

    gRecord[tag][fieldPosition][0] = gRecord[tag][fieldPosition][0].concat(subfields);
    redrawFields(tag);
    reColorFields();

    return ajaxData;
}

function performModifyHPChanges(changesList, isUndo){
  /** Undoing or redoing the operation of modifying the changeset
   */
  // first local updates
  gHoldingPenChanges = changesList;
  refreshChangesControls();
  var result = prepareOtherUpdateRequest(isUndo);
  result.undoRedo = isUndo ? "undo" : "redo";
  result.hpChanges = {toOverride: changesList};
  return result;
}

function hideUndoPreview(){
  $("#undoOperationVisualisationField").addClass("bibEditHiddenElement");
  // clearing the selection !
  $(".bibEditURDescEntrySelected").removeClass("bibEditURDescEntrySelected");
}

function getRedoOperation(){
  // getting the operation to be redoed
  currentElement = gRedoList[0];
  gRedoList.splice(0, 1);
  gUndoList.push(currentElement);
  return currentElement;
}

function getUndoOperation(){
  // getting the operation to be undoe
  currentElement = gUndoList[gUndoList.length - 1];
  gUndoList.splice(gUndoList.length - 1, 1);
  gRedoList.splice(0, 0, currentElement);
  return currentElement;
}

function setAllUnselected(){
  // make all the fields and subfields deselected
  setSelectionStatusAll(false);
}

function setSelectionStatusAll(status){
  // Changing the selection status for all the fields
  subfieldBoxes = $('.bibEditBoxSubfield');
  subfieldBoxes.each(function(e){
    if (subfieldBoxes[e].checked != status){
      subfieldBoxes[e].click();
    }
  });
}

function prepareApplyAllHPChangesHandler(){
    // a container for many undo/redo operations in the same time
    throw 'To implement';
}


/*** Handlers for specific operations*/

function renderURList(list, idPrefix, isInverted){
  // rendering the view of undo/redo list into a human-readible HTML
  // list -> an undo or redo list
  // idPrefix -> te prefix of the DOM identifier

  var result = "";
  var isPair = false;
  var helperCnt = 0;

  var iterationBeginning = list.length - 1;
  var iterationJump = -1;
  var iterationEnd = -1;

  if (isInverted === true){
    iterationBeginning = 0;
    iterationJump = 1;
    iterationEnd = list.length;
  }

  for (entryInd = iterationBeginning ; entryInd != iterationEnd ; entryInd += iterationJump){
      result += "<div class=\"" + (isPair ? "bibEditURPairRow" : "bibEditUROddRow" )+ " bibEditURDescEntry\" id=\"" + idPrefix + "_" + helperCnt + "\">";
      result += getHumanReadableUREntry(list[entryInd]);
      result += "</div>";
      isPair = ! isPair;
      helperCnt += 1;
  }
  result += "";
  return result;
}

function prepareApplyHPChangeHandler(){
    // A handler for HoldingPen change application/rejection
    throw 'to implement';
}

function processURUntil(entry){
  // Executing the bulk undo/redo
  var idParts = $(entry).attr("id").split("_");
  var index = parseInt(idParts[1]);

  if (idParts[0] == "undo"){
    undoMany(index+1);
  }
  else{
    redoMany(index+1);
  }
}

function prepareUndoHandlerChangeSubfield(tag, fieldPos, subfieldPos, oldVal, newVal, oldCode, newCode){
  var result = {};
  result.operation_type = "change_content";
  result.tag = tag;
  result.oldVal = oldVal;
  result.newVal = newVal;
  result.oldCode = oldCode;
  result.newCode = newCode;
  result.fieldPos = fieldPos;
  result.subfieldPos = subfieldPos;
  return result;
}

function setAllSelected(){
  // make all the fields and subfields selected
  setSelectionStatusAll(true);
}

function showUndoPreview(){
  $("#undoOperationVisualisationField").removeClass("bibEditHiddenElement");
}

function prepareUndoHandlerMoveSubfields(tag, fieldPosition, subfieldPosition, direction){
  var result = {};
  result.operation_type = "move_subfield";
  result.tag = tag;
  result.field_position = fieldPosition;
  result.subfield_position = subfieldPosition;
  result.direction = direction;
  return result;
}
// Handlers to implement:

function setFieldUnselected(tag, fieldPos){
  // unselect a given field
  setSelectionStatusField(tag, fieldPos, false);
}

function urPerformRemoveField(tag, position, isUndo){
  var toDeleteData = {};
  var toDeleteTmp = {};
  toDeleteTmp[position] = [];
  toDeleteData[tag] =  toDeleteTmp;

  // first preparing the data of Ajax request

  var ajaxData = {
    recID: gRecID,
    requestType: 'deleteFields',
    toDelete: toDeleteData,
    undoRedo: (isUndo ? "undo": "redo")
  };

  // updating the local model
  gRecord[tag].splice(position,1);
  if (gRecord[tag] == []){
    gRecord[tag] = undefined;
  }
  redrawFields(tag);
  reColorFields();

  return ajaxData;
}

function prepareOtherUpdateRequest(isUndo){
  return {
    requestType : 'otherUpdateRequest',
    recID : gRecID,
      undoRedo: ((isUndo === true) ? "undo" : "redo"),
    hpChanges: {}
  };
}

function performUndoApplyHpChanges(subRequests, oldChanges){
  /**
   Arguemnts:
     subRequests - subrequests performing the appropriate undo operations
   */

  // removing all teh undo/redo informations as they should be passed globally
  for (ind in subRequests){
      subRequests[ind].undoRedo = undefined;
  }
//  var gHoldingPenChanges
  return {
    requestType: 'applyBulkUpdates',
    undoRedo: "undo",
    requestsData: subRequests,
    hpChanges: {toOverride: oldChanges}
  };
}

function performBulkOperation(subHandlers, isUndo){
  /**
   return the bulk operation
   Arguments:
     subReqs : requests performing the sub-operations
     isUndo - is current request undo or redo ?
   */
  var subReqs = [];
  if (isUndo === true){
    subReqs = preparePerformUndoOperations(subHandlers);
  } else {
    // We can not simply assign and revers as the original would be modified
    var handlers = [];
    for (handlerInd = subHandlers.length -1; handlerInd >= 0; handlerInd--){
      handlers.push(subHandlers[handlerInd]);
    }
    subReqs = preparePerformRedoOperations(handlers);
  }

  for (ind in subReqs){
    subReqs[ind].undoRedo = undefined;
  }

  return {
    requestType: 'applyBulkUpdates',
    undoRedo: (isUndo === true ? "undo" : "redo"),
    requestsData: subReqs,
    hpChanges: {}
  };
}

function preparePerformRedoOperations(operations){
  /** Redos an operation passed as an argument */
  var ajaxRequestsData = [];
  for (operationInd in operations){
    var operation = operations[operationInd];
    var ajaxData = {};
    var isMultiple = false; // is the current decription a list of descriptors ?
    switch (operation.operation_type){
    case "no_operation":
      ajaxData = prepareOtherUpdateRequest(false);
      break;
    case "change_content":
      ajaxData = urPerformChangeSubfieldContent(operation.tag,
                                     operation.fieldPos,
                                     operation.subfieldPos,
                                     operation.newCode,
                                     operation.newVal,
                                     false);
      break;
    case "add_field":
      ajaxData = urPerformAddField(operation.isControlField,
                        operation.fieldPosition,
                        operation.tag,
                        operation.ind1,
                        operation.ind2,
                        operation.subfields,
                        operation.value,
                        false);
      break;
     case "add_subfields":
       ajaxData = urPerformAddSubfields(operation.tag,
                             operation.fieldPosition,
                             operation.newSubfields,
                             false);
       break;

    case "delete_fields":
      ajaxData = urPerformDeletePositionedFieldsSubfields(operation.toDelete, false);
      break;

    case "move_field":
      ajaxData = performMoveField(operation.tag, operation.field_position, operation.direction , false);
      break;
    case "move_subfield":
      ajaxData = performMoveSubfield(operation.tag, operation.field_position, operation.subfield_position, operation.direction, false);
      break;
    case "bulk_operation":
      ajaxData = performBulkOperation(operation.handlers, false);
      break;
    case "apply_hp_change":
      removeViewedChange(operation.changeNo); // we redo the change application so the change itself gets removed
      ajaxData = preparePerformRedoOperations([operation.handler]);
      ajaxData[0].hpChange = {};
      ajaxData[0].hpChange.toDisable = [operation.changeNo]; // reactivate this change
      isMultiple = true;
      break;

    case "apply_hp_changes":
      // in this case many changes are applied at once and the list of changes is completely overriden
      ajaxData = performUndoApplyHpChanges();
    case "change_field":
      ajaxData = urPerformChangeField(operation.tag, operation.fieldPos,
                                      operation.newInd1, operation.newInd2,
                                      operation.newSubfields,
                                      operation.newIsControlField,
                                      operation.oldValue , false);
      break;
    case "visualize_hp_changeset":
      ajaxData = prepareVisualizeChangeset(operation.changesetNumber,
        operation.newChangesList, "redo");
      break;
    case "remove_all_hp_changes":
      ajaxData = performModifyHPChanges([], false);
      break;

    default:
      alert("Error: wrong operation to redo");
      break;
    }
    // now dealing with the results
    if (isMultiple){
      // in this case we have to merge lists rather than include inside
      for (elInd in ajaxData){
        ajaxRequestsData.push(ajaxData[elInd]);
      }
    }
    else{
      ajaxRequestsData.push(ajaxData);
    }
  }
  return ajaxRequestsData;
}

function performRedoOperations(operations){
  ajaxRequestsData = preparePerformRedoOperations(operations);
  // now submitting the bulk request
  var optArgs = {
//      undoRedo: "redo"
  };

  createBulkReq(ajaxRequestsData, function(json){
    updateStatus('report', gRESULT_CODES[json['resultCode']]);
  }, optArgs);
}

function prepareUndoHandlerDeleteFields(toDelete){
  /*Creating Undo/Redo handler for the operation of removal of fields and/or subfields
    Arguments: toDelete - indicates fields and subfields scheduled to be deleted.
      this argument should have a following structure:
      {
        "fields" : { tag: {fieldsPosition: field_structure_similar_to_on_from_gRecord}}
        "subfields" : {tag: { fieldPosition: { subfieldPosition: [code, value]}}}
      }
  */
  var result = {};
  result.operation_type = "delete_fields";
  result.toDelete = toDelete;
  return result;
}

function setSubfieldUnselected(tag, fieldPos, subfieldPos){
 // unseelcting a subfield
  setSelectionStatusSubfield(tag, fieldPos, subfieldPos, false);
}


function prepareUndoHandlerAddSubfields(tag, fieldPosition, subfields){
  /**
    tag : tag of the field inside which the fields should be added
    fieldPosition: position of the field
    subfields: new subfields to be added. This argument should be a list
      of lists representing a single subfield. Each subfield is represented
      by a list, containing 2 elements. [subfield_code, subfield_value]
  */
  var result = {};
  result.operation_type = "add_subfields";
  result.tag = tag;
  result.fieldPosition = fieldPosition;
  result.newSubfields = subfields;
  return result;
}

function setFieldSelected(tag, fieldPos){
  // select a given field
  setSelectionStatusField(tag, fieldPos, true);
}

function redoMany(number){
  // redoing an indicated number of operations
  var redoOperations = [];
  for (i=0;i<number;i++){
    redoOperations.push(getRedoOperation());
  }
  performRedoOperations(redoOperations);
  updateUrView();
}
function urPerformAddField(controlfield, fieldPosition, tag, ind1, ind2, subfields, value, isUndo){
  var ajaxData = {
    recID: gRecID,
    requestType: 'addField',
    controlfield: controlfield,
    fieldPosition: fieldPosition,
    tag: tag,
    ind1: ind1,
    ind2: ind2,
    subfields: subfields,
    value: value,
    undoRedo: (isUndo? "undo": "redo")
  };

//  createReq(data, function(json){
//    updateStatus('report', gRESULT_CODES[json['resultCode']]);
//  });

  // updating the local situation
  if (gRecord[tag] == undefined){
    gRecord[tag] = [];
  }
  var newField = [(controlfield ? [] : subfields), ind1, ind2,
                  (controlfield ? value: ""), 0];
  gRecord[tag].splice(fieldPosition, 0, newField);
  redrawFields(tag);
  reColorFields();

  return ajaxData;
}

function urPerformRemoveSubfields(tag, fieldPosition, subfields, isUndo){
  var toDelete = {};
  toDelete[tag] = {};
  toDelete[tag][fieldPosition] = []
  var startingPosition = gRecord[tag][fieldPosition][0].length - subfields.length;
  for (var i=startingPosition; i<gRecord[tag][fieldPosition][0].length ; i++){
    toDelete[tag][fieldPosition].push(i);
  }

  var ajaxData = {
    recID: gRecID,
    requestType: 'deleteFields',
    toDelete: toDelete,
    undoRedo: (isUndo ? "undo": "redo")
  };

//  createReq(data, function(json){
//    updateStatus('report', gRESULT_CODES[json['resultCode']]);
//  });
  // modifying the client-side interface
  gRecord[tag][fieldPosition][0].splice( gRecord[tag][fieldPosition][0].length - subfields.length, subfields.length);
  redrawFields(tag);
  reColorFields();

  return ajaxData;
}

function updateUrView(){
  /*Updating the information box in the bibEdit menu
    (What are the current undo/redo handlers*/
  $('#undoOperationVisualisationFieldContent')[0].innerHTML = (gUndoList.length == 0) ? "(empty)" :
        renderURList(gUndoList, "undo");
//        gUndoList[gUndoList.length - 1].operation_type;
  $('#redoOperationVisualisationFieldContent')[0].innerHTML = (gRedoList.length == 0) ? "(empty)" :
        renderURList(gRedoList, "redo", true);

  // now attaching the events ... the function is uniform for all the elements present inside the document

    var urEntries = $('.bibEditURDescEntry');
    urEntries.each(function(index){
        $(urEntries[index]).bind("mouseover", function (e){
          $(urEntries[index]).find(".bibEditURDescEntryDetails").removeClass("bibEditHiddenElement");
            urMarkSelectedUntil(urEntries[index]);
        });
        $(urEntries[index]).bind("mouseout", function(e){
          $(urEntries[index]).find(".bibEditURDescEntryDetails").addClass("bibEditHiddenElement");
        });
        $(urEntries[index]).bind("click", function(e){
            processURUntil(urEntries[index]);
        });
    });
}

function performMoveSubfield(tag, fieldPosition, subfieldIndex, direction, undoRedo){
  var newSubfieldIndex = parseInt(subfieldIndex) + (direction == "up" ? -1 : 1);
  var fieldID = tag + '_' + fieldPosition;
  var field = gRecord[tag][fieldPosition];
  var subfields = field[0];

  // Create Ajax request.
  var ajaxData = {
    recID: gRecID,
    requestType: 'moveSubfield',
    tag: tag,
    fieldPosition: fieldPosition,
    subfieldIndex: subfieldIndex,
    newSubfieldIndex: newSubfieldIndex,
    undoRedo: (undoRedo == true) ?  "undo" : ((undoRedo == false) ?  "redo" : undoRedo)
  };

  // Continue local updating.
  var subfieldToSwap = subfields[newSubfieldIndex];
  subfields[newSubfieldIndex] = subfields[subfieldIndex];
  subfields[subfieldIndex] = subfieldToSwap;
  var rowGroup = $('#rowGroup_' + fieldID);
  var coloredRowGroup = $(rowGroup).hasClass('bibEditFieldColored');
  $(rowGroup).replaceWith(createField(tag, field, fieldPosition));
  if (coloredRowGroup)
    $('#rowGroup_' + fieldID).addClass('bibEditFieldColored');

  // taking care of having only the new subfield position selected
  setAllUnselected();
  setSubfieldSelected(tag, fieldPosition, newSubfieldIndex);

  return ajaxData;
}

function onRedo(evt){
  if (gRedoList.length <= 0){
    alert("No Redo operations to process");
    return;
  }
  redoMany(1);
}

// functions related to the automatic field selection/unseletion

function hideRedoPreview(){
  $("#redoOperationVisualisationField").addClass("bibEditHiddenElement");
  // clearing the selection !
  $(".bibEditURDescEntrySelected").removeClass("bibEditURDescEntrySelected");
}

function urPerformAddPositionedFieldsSubfields(toAdd, isUndo){
  return createFields(toAdd, isUndo);
}

function setSubfieldSelected(tag, fieldPos, subfieldPos){
  // selecting a subfield
  setSelectionStatusSubfield(tag, fieldPos, subfieldPos, true);
}

function getHumanReadableUREntry(handler){
  // rendering a human readable description of an undo/redo operation
  // handler : the u/r handler to render
  var operationDescription;

  switch (handler.operation_type){
    case "move_field":
      operationDescription = "move field";
      break;
    case "move_field":
      operationDescription = "change field";
      break;
    case "move_subfield":
      operationDescription = "move subfield";
      break;
    case "change_content":
      operationDescription = "edit subfield";
      break;
    case "add_field":
      operationDescription = "add field";
      break;
    case "add_subfields":
      operationDescription = "add field";
      break;
    case "delete_fields":
      operationDescription = "delete";
      break;
    case "bulk_operation":
      operationDescription = handler.title;
      break;
    case "apply_hp_change":
      operationDescription = "holding pen";
      break;
    case "visualize_hp_changeset":
      operationDescription = "show changes";
      break;
    case "remove_all_hp_changes":
      operationDescription = "remove changes";
      break;
    default:
      operationDescription = "unknown operation";
      break;
  }

  // now rendering parameters of the handler
  var readableDescriptors = {
    'tag' : 'tag',
    'operation_type' : false,
    'field_position' : 'field position',
    'subfield_position' : 'subfield position',
    'newVal' : 'new value',
    'oldVal' : 'old value',
    'fieldPos' : 'field position',
    'toDelete' : false,
    'handlers' : false
  };

  var handlerDetails = '<table>';

  for (characteristic in handler){
    if (readableDescriptors[characteristic] != false){
      var characteristicString = characteristic;
      if (readableDescriptors[characteristic] != undefined){
          characteristicString = readableDescriptors[characteristic];
      }
      handlerDetails += '<tr><td class="bibEditURDescChar">'
        + characteristicString + ':</td><td>' + handler[characteristic]  + '</td></tr>';
    }
  }

  handlerDetails += '</table>';
  // now generating the final result
  return '<div class="bibEditURDescHeader">'
    + operationDescription + '</div><div class="bibEditURDescEntryDetails bibEditHiddenElement">'
    + handlerDetails + '</div>';
}

function urMarkSelectedUntil(entry){
    // marking all the detailed entries, until a given one as selected
    //  these entries have the same prefix but a smaller number
    var identifierParts = $(entry).attr("id").split("_");
    var position = parseInt(identifierParts[1]);
    var potentialElements = $(".bibEditURDescEntry");
    potentialElements.each(function(index){
        var curIdentifierParts = $(potentialElements[index]).attr("id").split("_");
        if ((curIdentifierParts[0] == identifierParts[0]) && (parseInt(curIdentifierParts[1]) <= position)){
           $(potentialElements[index]).addClass("bibEditURDescEntrySelected");
        }
        else {
           $(potentialElements[index]).removeClass("bibEditURDescEntrySelected");
        }
    });
}

function onUndo(evt){
  if (gUndoList.length <= 0){
    alert("No Undo operations to process");
    return;
  }
  undoMany(1);
}

function preparePerformUndoOperations(operations){
  /** Undos an operation passed as an argument */
  var ajaxRequestsData = [];
  for (operationInd in operations){
    var operation = operations[operationInd];
    var action = null;
    var actionData = null;
    var ajaxData = {};
    var isMultiple = false; // is the current oepration handler a list
      // of operations rather than a single op ?

    switch (operation.operation_type){
    case "no_operation":
      ajaxData = prepareOtherUpdateRequest(true);
      break;
    case "change_content":
      ajaxData = urPerformChangeSubfieldContent(operation.tag,
                                     operation.fieldPos,
                                     operation.subfieldPos,
                                     operation.oldCode,
                                     operation.oldVal,
                                     true);
      break;
    case "add_field":
      ajaxData = urPerformRemoveField(operation.tag,
                            operation.fieldPosition,
                                              true);
      break;
    case "add_subfields":
      ajaxData = urPerformRemoveSubfields(operation.tag,
                               operation.fieldPosition,
                               operation.newSubfields,
                               true);
      break;

    case "delete_fields":
      ajaxData = urPerformAddPositionedFieldsSubfields(operation.toDelete, true);
      break;

    case "move_field":
      var newDirection = "up";
      var newPosition = operation.field_position + 1;
      if (operation.direction == "up"){
        newDirection = "down";
        newPosition = operation.field_position - 1;
      }

      ajaxData = performMoveField(operation.tag, newPosition, newDirection, true);
      break;
    case "move_subfield":

      var newDirection = "up";
      var newPosition = operation.subfield_position + 1;
      if (operation.direction == "up"){
        newDirection = "down";
        newPosition = operation.subfield_position - 1;
      }
      ajaxData = performMoveSubfield(operation.tag, operation.field_position,
        newPosition, newDirection, true);
      break;
    case "bulk_operation":
      ajaxData = performBulkOperation(operation.handlers, true);
      break;
    case "apply_hp_change":
      ajaxData = preparePerformUndoOperations([operation.handler]);
      ajaxData[0]["hpChange"] = {};
      ajaxData[0]["hpChange"]["toEnable"] = [operation.changeNo]; // reactivate
      isMultiple = true;
      revertViewedChange(operation.changeNo);
      break;
    case "visualize_hp_changeset":
      ajaxData = prepareUndoVisualizeChangeset(operation.changesetNumber,
        operation.oldChangesList);
      break;
    case "change_field":
      ajaxData = urPerformChangeField(operation.tag, operation.fieldPos,
                                      operation.oldInd1, operation.oldInd2,
                                      operation.oldSubfields,
                                      operation.oldIsControlField,
                                      operation.oldValue , true);
      break;
    case "remove_all_hp_changes":
      ajaxData = performModifyHPChanges(operation.old_changes_list, true);
      break;
    default:
      alert("Error: wrong operation to undo");
    }

    if (isMultiple){
      // in this case we have to merge lists rather than include inside
      for (elInd in ajaxData){
        ajaxRequestsData.push(ajaxData[elInd]);
      }
    }
    else{
      ajaxRequestsData.push(ajaxData);
    }
  }

  return ajaxRequestsData;
}

function performUndoOperations(operations){
  var ajaxRequestsData = preparePerformUndoOperations(operations);
  // now submitting the ajax request
  var optArgs={
//    undoRedo: "undo"
  };

  createBulkReq(ajaxRequestsData, function(json){
    updateStatus('report', gRESULT_CODES[json['resultCode']]);
  }, optArgs);
}

function prepareUndoHandlerMoveField(tag, fieldPosition, direction){
  var result = {};
  result.tag = tag;
  result.operation_type = "move_field";
  result.field_position = fieldPosition;
  result.direction = direction;
  return result;
}

function prepareUndoHandlerChangeField(tag, fieldPos,
  oldInd1, oldInd2, oldSubfields, oldIsControlField, oldValue,
  newInd1, newInd2, newSubfields, newIsControlField, newValue){
  /** Function building a handler allowing to undo the operation of
      changing the field structure.

      Changing can happen only if tag and position remain the same,
      Otherwise we deal with removal and adding of a field

      Arguments:
        tag - tag of a field
        fieldPos - position of a field

        oldInd1, oldInd2 - indices of the old field
        oldSubfields - subfields present int the old structure
        oldIsControlField - a boolean value indicating if the field
                            is a control field
        oldValue - a value before change in case of field being a control field.
                   if the field is normal field, this should be equal ""

        newInd1, newInd2, newSubfields, newIsControlField, newValue -
           Similar parameters describing new structure of a field
  */
  var result = {};
  result.operation_type = "change_field";
  result.tag = tag;
  result.fieldPos = fieldPos;
  result.oldInd1 = oldInd1;
  result.oldInd2 = oldInd2;
  result.oldSubfields = oldSubfields;
  result.oldIsControlField = oldIsControlField;
  result.oldValue = oldValue;
  result.newInd1 = newInd1;
  result.newInd2 = newInd2;
  result.newSubfields = newSubfields;
  result.newIsControlField = newIsControlField;
  result.newValue = newValue;

  return result;
}

function showRedoPreview(){
  $("#redoOperationVisualisationField").removeClass("bibEditHiddenElement");
}

function deleteFields(toDeleteStruct, undoRedo){
  // a function deleting the specified fields on both client and server sides
  //
  // toDeleteFields : a structure describing fields and subfields to delete
  //   this structure is the same as for the function createFields

  var toDelete = {};

  // first we convert the data into a different format, loosing the informations about
  //   subfields of entirely removed fields

  // first the entirely deleted fields
  for (tag in toDeleteStruct.fields){
    if (toDelete[tag] == undefined){
      toDelete[tag] = {};
    }
    for (fieldPos in toDeleteStruct.fields[tag]){
      toDelete[tag][fieldPos] = [];
    }
  }

  for (tag in toDeleteStruct.subfields){
    if (toDelete[tag] == undefined){
      toDelete[tag] = {};
    }
    for (fieldPos in toDeleteStruct.subfields[tag]){
      toDelete[tag][fieldPos] = [];
      for (subfieldPos in toDeleteStruct.subfields[tag][fieldPos]){
        toDelete[tag][fieldPos].push(subfieldPos);
      }
    }
  }

  var tagsToRedraw = [];

  // reColorTable is true if any field are completely deleted.
  var reColorTable = false;

  // first we have to encode all the data in a single dictionary

  // Create Ajax request.
  var ajaxData = {
    recID: gRecID,
    requestType: 'deleteFields',
    toDelete: toDelete,
    undoRedo: (undoRedo == true) ? "undo" : ((undoRedo == false) ? "redo" : undoRedo)
  };

  // Continue local updating.
  // Parse data structure and delete accordingly in record.
  var fieldsToDelete, subfieldIndexesToDelete, field, subfields, subfieldIndex;
  for (var tag in toDelete) {
    tagsToRedraw.push(tag);
    fieldsToDelete = toDelete[tag];
    // The fields should be treated in the decreasing order (during the removal, indices may change)
    traversingOrder = [];
    for (fieldPosition in fieldsToDelete) {
      traversingOrder.push(fieldPosition);
    }
    // normal sorting will do this in a lexycographical order ! (problems if > 10 subfields
    // function provided, allows sorting in the reversed order
    var traversingOrder = traversingOrder.sort(function(a, b){
      return b - a;
    });

    for (var fieldInd in traversingOrder) {
      var fieldPosition = traversingOrder[fieldInd];
      var fieldID = tag + '_' + fieldPosition;
      subfieldIndexesToDelete = fieldsToDelete[fieldPosition];
      if (subfieldIndexesToDelete.length == 0)
        deleteFieldFromTag(tag, fieldPosition);
      else {
        // normal sorting will do this in a lexycographical order ! (problems if > 10 subfields
        subfieldIndexesToDelete.sort(function(a, b){
          return a - b;
        });
        field = gRecord[tag][fieldPosition];
        subfields = field[0];
        for (var j = subfieldIndexesToDelete.length - 1; j >= 0; j--){
          subfields.splice(subfieldIndexesToDelete[j], 1);
        }
      }
    }
  }

  // If entire fields has been deleted, redraw all fields with the same tag
  // and recolor the full table.
  for (tag in tagsToRedraw)
      redrawFields(tagsToRedraw[tag]);
  reColorFields();

  return ajaxData;
}

function getSelectedFields(){
  /** Function returning a list of selected fields
    Returns all the fields and subfields that are slected.
    The structure of a result is following:
    {
      "fields" : { tag: {fieldsPosition: field_structure_similar_to_on_from_gRecord}}
      "subfields" : {tag: { fieldPosition: { subfieldPosition: [code, value]}}}
    }
  */
  var selectedFields = {};
  var selectedSubfields = {};

  var checkedFieldBoxes = $('input[class="bibEditBoxField"]:checked');
  var checkedSubfieldBoxes = $('input[class="bibEditBoxSubfield"]:checked');

  if (!checkedFieldBoxes.length && !checkedSubfieldBoxes.length)
    // No fields selected
    return;

  // Collect fields to be deleted in toDelete.
  $(checkedFieldBoxes).each(function(){
    var tmpArray = this.id.split('_');
    var tag = tmpArray[1], fieldPosition = tmpArray[2];
    if (!selectedFields[tag]) {
      selectedFields[tag] = {};
    }
    selectedFields[tag][fieldPosition] = gRecord[tag][fieldPosition];
  });

  // Collect subfields to be deleted in toDelete.
  $(checkedSubfieldBoxes).each(function(){
    var tmpArray = this.id.split('_');
    var tag = tmpArray[1], fieldPosition = tmpArray[2], subfieldIndex = tmpArray[3];
    if (selectedFields[tag] == undefined || selectedFields[tag][fieldPosition] == undefined){
      // this field has not been selected entirely, we can proceed with processing subfield slection
      if (!selectedSubfields[tag]) {
        selectedSubfields[tag] = {};
        selectedSubfields[tag][fieldPosition] = {};
        selectedSubfields[tag][fieldPosition][subfieldIndex] =
          gRecord[tag][fieldPosition][0][subfieldIndex];
      }
      else {
        if (!selectedSubfields[tag][fieldPosition])
          selectedSubfields[tag][fieldPosition] = {};
        selectedSubfields[tag][fieldPosition][subfieldIndex] =
          gRecord[tag][fieldPosition][0][subfieldIndex];
      }
    } else {
      // this subfield is a part of entirely selected field... we have already included the information about subfields
    }
  });
  var result={};
  result.fields = selectedFields;
  result.subfields = selectedSubfields;
  return result;
}

function urPerformChangeSubfieldContent(tag, fieldPos, subfieldPos, code, val, isUndo){
  // changing the server side model
  var ajaxData = {
    recID: gRecID,
    requestType: 'modifyContent',
    tag: tag,
    fieldPosition: fieldPos,
    subfieldIndex: subfieldPos,
    subfieldCode: code,
    value: val,
    undoRedo: (isUndo ? "undo": "redo")
  };
//  createReq(data, function(json){
//    updateStatus('report', gRESULT_CODES[json['resultCode']]);
//  });

  // changing the model
  gRecord[tag][fieldPos][0][subfieldPos][0] = code;
  gRecord[tag][fieldPos][0][subfieldPos][1] = val;

  // changing the display .... what if being edited right now ?
  redrawFields(tag);
  reColorFields();

  return ajaxData;
}


function performChangeField(tag, fieldPos, ind1, ind2, subFields, isControlfield,
  value, undoRedo){
  /** Function changing the field structure and generating an appropriate AJAX
      request handler
      Arguments:
        tag, fieldPos, ind1, ind2, subFields, isControlfield, value - standard
          values describing a field. tag, fieldPos are used to locate the field
          instance (which has to exist) and its content is modified accordingly.
        undoRedo - a undoRedo Handler or one of the words "undo"/"redo"
   */
  var ajaxData = {
    recID: gRecID,
    requestType: "modifyField",
    controlfield : isControlfield,
    fieldPosition : fieldPos,
    ind1: ind1,
    ind2: ind2,
    tag: tag,
    subFields: subFields,
    undoRedo : undoRedo,
    hpChanges: {}
  }

  // local changes
  gRecord[tag][fieldPos][0] = subFields;
  gRecord[tag][fieldPos][1] = ind1;
  gRecord[tag][fieldPos][2] = ind2;
  gRecord[tag][fieldPos][3] = value;
  redrawFields(tag);
  reColorFields();

  return ajaxData;
}

function urPerformChangeField(tag, fieldPos, ind1, ind2, subFields,
  isControlfield, value, isUndo){
  /**
   */
  return performChangeField(tag, fieldPos, ind1, ind2, subFields,
    isControlfield, value, (isUndo ? "undo" : "redo"));
}

function performMoveField(tag, oldFieldPosition, direction, undoRedo){
  var newFieldPosition = oldFieldPosition + (direction == "up" ? -1 : 1);
  // Create Ajax request.
  var ajaxData = {
    recID: gRecID,
    requestType: 'moveField',
    tag: tag,
    fieldPosition: oldFieldPosition,
    direction: direction,
    undoRedo: (undoRedo == true) ? "undo" : ((undoRedo == false) ? "redo" : undoRedo)
  };

  //continue updating locally
  var currentField = gRecord[tag][oldFieldPosition];
  gRecord[tag][oldFieldPosition] = gRecord[tag][newFieldPosition];
  gRecord[tag][newFieldPosition] = currentField;

  $('tbody#rowGroup_'+tag+'_'+(newFieldPosition)).replaceWith(
      createField(tag, gRecord[tag][newFieldPosition], newFieldPosition));
  $('tbody#rowGroup_'+tag+'_'+oldFieldPosition).replaceWith(
      createField(tag, gRecord[tag][oldFieldPosition], oldFieldPosition));

  reColorFields();

  // Now taking care of having the new field selected and the rest unselected
  setAllUnselected();
  setFieldSelected(tag, newFieldPosition);
//$('#boxField_'+tag+'_'+(newFieldPosition)).click();
  return ajaxData;
}

function setSelectionStatusField(tag, fieldPos, status){
  var fieldCheckbox = $('#boxField_' + tag + '_' + fieldPos);
  var subfieldCheckboxes = $('#rowGroup_' + tag + '_' + fieldPos + ' .bibEditBoxSubfield');

  fieldCheckbox.each(function(ind){
      if (fieldCheckbox[ind].checked != status)
      {
          fieldCheckbox[ind].click();
      }
  });
}

function urPerformDeletePositionedFieldsSubfields(toDelete, isUndo){
  return deleteFields(toDelete, isUndo);
}

/** General Undo/Redo treatment lists */

function setSelectionStatusSubfield(tag, fieldPos, subfieldPos, status){
  var subfieldCheckbox = $('#boxSubfield_' + tag + '_' + fieldPos + '_' + subfieldPos);
  if (subfieldCheckbox[0].checked != status)
  {
      subfieldCheckbox[0].click();
  }
}

function createFields(toCreateFields, isUndo){
  // a function adding fields.
  // toCreateFields : a structure describing fields and subfields to create
  //   this structure is the same as for the function deleteFields

  // 1) Preparing the AJAX request
  var tagsToRedraw = {}
  var ajaxData = {
    recID: gRecID,
    requestType: 'addFieldsSubfieldsOnPositions',
    fieldsToAdd: toCreateFields.fields,
    subfieldsToAdd: toCreateFields.subfields
  };

  if (isUndo != undefined){
    ajaxData['undoRedo'] = (isUndo ? "undo": "redo");
  }

  // 2) local processing -> creating the fields locally
  //   - first creating the missing fields so all the subsequent field indices are correcr
  for (tag in toCreateFields.fields){
    if (gRecord[tag] == undefined){
      gRecord[tag] = [];
    }
    tagsToRedraw[tag] = true;
    var fieldIndices = [];
    for (fieldPos in toCreateFields.fields[tag]){
      fieldIndices.push(fieldPos);
    }
    fieldIndices.sort(); // we have to add fields in the increasing order
      for (indInd in fieldIndices){
        var fieldIndexToAdd = fieldIndices[indInd]; // index of the field index to add in the indices array
        var newField = toCreateFields.fields[tag][fieldIndexToAdd];
        gRecord[tag].splice(fieldIndexToAdd, 0, newField);
      }
  }

  //   - now appending the remaining subfields

  for (tag in toCreateFields.subfields){
    tagsToRedraw[tag] = true;
    for (fieldPos in toCreateFields.subfields[tag]){
      var subfieldPositions = [];
      for (subfieldPos in toCreateFields.subfields[tag][fieldPos]){
        subfieldPositions.push(subfieldPos);
      }
      subfieldPositions.sort();
      for (subfieldInd in subfieldPositions){
        subfieldPosition = subfieldPositions[subfieldInd];
        gRecord[tag][fieldPos][0].splice(
          subfieldPosition, 0,
          toCreateFields.subfields[tag][fieldPos][subfieldPosition]);
      }
    }
  }

  // - redrawint the affected tags

  for (tag in tagsToRedraw){
   redrawFields(tag);
  }
  reColorFields();

  return ajaxData;
}


/* Bibcirculation Panel functions */

function isBibCirculationPanelNecessary(){
  /** A function checking if the BibCirculation connectivity panel should
      be displayed. This information is derieved from the state of the record.
      Returns true or false
  */

  if (gRecID === null){
    return false;
  }

  // only if the record is saved and exists in the database and belongs
  // to a particular colelction
  return gDisplayBibCircPanel;
}


function updateBibCirculationPanel(){
  /** Updates the BibCirculation panel contents and visibility
  */
  if (gDisplayBibCircPanel === false){
    // in case, the panel is present, should be hidden
    $("#bibEditBibCircConnection").addClass("bibEditHiddenElement");
  }
  else {
    // the panel must be present - we have to show it
    $(".bibEditBibCircConnection").removeClass("bibEditHiddenElement");
  }

  var interfaceElement = $("#bibEditBibCircConnection");
  if (isBibCirculationPanelNecessary()){
    interfaceElement.removeClass("bibEditHiddenElement");
  } else {
    interfaceElement.addClass("bibEditHiddenElement");
  }

  // updating the content
  var copiesCountElement = $('#bibEditBibCirculationCopies');
  copiesCountElement.attr("innerHTML", gPhysCopiesNum);
}

function bibCircIntGetEditCopyUrl(recId){
  /**A function returning the address under which, the edition of a
      given record is possible
  */

//  return "/admin/bibcirculation/bibcirculationadmin.py/get_item_details?recid=" + recId;
  return gBibCircUrl;
}


function onBibCirculationBtnClicked(e){
  /** A function redirecting the user to the BibCiculation web interface
  */
  var link = bibCircIntGetEditCopyUrl(gRecID);
  window.open(link);
}
